--- /dev/null
+/**\r
+ * This document is a part of the source code and related artifacts\r
+ * for CollectionSpace, an open source collections management system\r
+ * for museums and related institutions:\r
+ *\r
+ * http://www.collectionspace.org\r
+ * http://wiki.collectionspace.org\r
+ *\r
+ * Copyright (c) 2009 Regents of the University of California\r
+ *\r
+ * Licensed under the Educational Community License (ECL), Version 2.0.\r
+ * You may not use this file except in compliance with this License.\r
+ *\r
+ * You may obtain a copy of the ECL 2.0 License at\r
+ * https://source.collectionspace.org/collection-space/LICENSE.txt\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.collectionspace.services.IntegrationTests.xmlreplay;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+/**\r
+ * User: laramie\r
+ * $LastChangedRevision: $\r
+ * $LastChangedDate: $\r
+ */\r
+public class ServiceResult {\r
+ public String testID = "";\r
+ public String testGroupID = "";\r
+ public String fullURL = "";\r
+ public String deleteURL = "";\r
+ public String location = "";\r
+ public String CSID = "";\r
+ public String subresourceCSID = "";\r
+ public String result = "";\r
+ public int responseCode = 0;\r
+ public String responseMessage = "";\r
+ public String method = "";\r
+ public String error = "";\r
+ public String fromTestID = "";\r
+ public String auth = "";\r
+ public List<Integer> expectedCodes = new ArrayList<Integer>();\r
+ public boolean codeInSuccessRange(int code){\r
+ if (0<=code && code<200){\r
+ return false;\r
+ } else if (400<=code) {\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+ public boolean gotExpectedResult(){\r
+ for (Integer oneExpected : expectedCodes){\r
+ if (responseCode == oneExpected){\r
+ return true;\r
+ }\r
+ }\r
+ if (expectedCodes.size()>0 && codeInSuccessRange(responseCode)){ //none found, but result expected.\r
+ for (Integer oneExpected : expectedCodes){\r
+ if ( ! codeInSuccessRange(oneExpected)){\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ return codeInSuccessRange(responseCode);\r
+ }\r
+ //public static final String[] DUMP_OPTIONS = {"minimal", "detailed", "full"};\r
+ public static enum DUMP_OPTIONS {minimal, detailed, full};\r
+\r
+ public String toString(){\r
+ return detail(true);\r
+\r
+ }\r
+ public String detail(boolean includePayloads){\r
+ return "{ServiceResult: "\r
+ + ( Tools.notEmpty(testID) ? " testID:"+testID : "" )\r
+ + ( Tools.notEmpty(testGroupID) ? "; testGroupID:"+testGroupID : "" )\r
+ + ( Tools.notEmpty(fromTestID) ? "; fromTestID:"+fromTestID : "" )\r
+ +"; "+method\r
+ +"; "+responseCode\r
+ + ( Tools.notEmpty(responseMessage) ? "; msg:"+responseMessage : "" )\r
+ +"; URL:"+fullURL\r
+ +"; auth: "+auth\r
+ + ( Tools.notEmpty(deleteURL) ? "; deleteURL:"+deleteURL : "" )\r
+ + ( Tools.notEmpty(location) ? "; location.CSID:"+location : "" )\r
+ + ( Tools.notEmpty(error) ? "; ERROR:"+error : "" )\r
+ + ( (expectedCodes.size()>0) ? "; expectedCodes:"+expectedCodes : "" )\r
+ + "; gotExpected:"+gotExpectedResult()\r
+ + ( includePayloads && Tools.notEmpty(result) ? "; result:"+result : "" )\r
+ +"}";\r
+ }\r
+ public String minimal(){\r
+ return "{"\r
+ + ( gotExpectedResult() ? "SUCCESS" : "FAILURE" )\r
+\r
+ + ( Tools.notEmpty(testID) ? "; "+testID : "" )\r
+ +"; "+method\r
+ +"; "+responseCode\r
+ + (expectedCodes.size()>0 ? "; expected:"+expectedCodes : "")\r
+ + ( Tools.notEmpty(responseMessage) ? "; msg:"+responseMessage : "" )\r
+ +"; URL:"+fullURL\r
+ +"; auth: "+auth\r
+ + ( Tools.notEmpty(error) ? "; ERROR:"+error : "" )\r
+ +"}";\r
+ }\r
+ public String dump(ServiceResult.DUMP_OPTIONS opt){\r
+ switch (opt){\r
+ case minimal:\r
+ return minimal();\r
+ case detailed:\r
+ return detail(false);\r
+ case full:\r
+ return detail(true);\r
+ default:\r
+ return toString();\r
+ }\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * This document is a part of the source code and related artifacts\r
+ * for CollectionSpace, an open source collections management system\r
+ * for museums and related institutions:\r
+ *\r
+ * http://www.collectionspace.org\r
+ * http://wiki.collectionspace.org\r
+ *\r
+ * Copyright (c) 2009 Regents of the University of California\r
+ *\r
+ * Licensed under the Educational Community License (ECL), Version 2.0.\r
+ * You may not use this file except in compliance with this License.\r
+ *\r
+ * You may obtain a copy of the ECL 2.0 License at\r
+ * https://source.collectionspace.org/collection-space/LICENSE.txt\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.collectionspace.services.IntegrationTests.xmlreplay;\r
+\r
+import org.apache.commons.jexl2.Expression;\r
+import org.apache.commons.jexl2.JexlContext;\r
+import org.apache.commons.jexl2.JexlEngine;\r
+import org.apache.commons.jexl2.MapContext;\r
+\r
+import java.io.File;\r
+import java.util.regex.Pattern;\r
+import java.util.regex.Matcher;\r
+\r
+/** General utility methods.\r
+ * @author Laramie Crocker\r
+ */\r
+public class Tools {\r
+ /** @return first glued to second with the separator string, at most one time - useful for appending paths.\r
+ */\r
+ public static String glue(String first, String separator, String second){\r
+ if (first==null) { first = ""; }\r
+ if (second==null) { second = ""; }\r
+ if (separator==null) { separator = ""; }\r
+ if (first.startsWith(separator) && second.startsWith(separator)){\r
+ return first.substring(0, first.length()-separator.length()) + second;\r
+ }\r
+ if (first.endsWith(separator) || second.startsWith(separator)){\r
+ return first+second;\r
+ }\r
+ return first+separator+second;\r
+ }\r
+\r
+ /** Handles null strings as empty. */\r
+ public static boolean isEmpty(String str){\r
+ return !notEmpty(str);\r
+ }\r
+\r
+ /** Handles null strings as empty. */\r
+ public static boolean notEmpty(String str){\r
+ if (str==null) return false;\r
+ if (str.length()==0) return false;\r
+ return true;\r
+ }\r
+\r
+ /** Handles null strings as false. */\r
+ public static boolean isTrue(String test){\r
+ return notEmpty(test) && (new Boolean(test)).booleanValue();\r
+ }\r
+\r
+ /* Example usage of searchAndReplace:\r
+ for (Map.Entry<String,String> entry : variablesMap.entrySet()){\r
+ String key = entry.getKey();\r
+ String replace = entry.getValue();\r
+ String find = "\\$\\{"+key+"\\}"; //must add expression escapes\r
+ //because $ and braces are "special", and we want to find "${object.CSID}"\r
+ uri = Tools.searchAndReplace(uri, find, replace);\r
+ System.out.println("---- REPLACE.uri: "+initURI);\r
+ System.out.println("---- REPLACE.find: "+find);\r
+ System.out.println("---- REPLACE.replace: "+replace);\r
+ System.out.println("---- REPLACE.uri result: "+uri);\r
+ }\r
+ */\r
+ public static String searchAndReplace(String source, String find, String replace){\r
+ Pattern pattern = Pattern.compile(find);\r
+ Matcher matcher = pattern.matcher(source);\r
+ String output = matcher.replaceAll(replace);\r
+ return output;\r
+ }\r
+\r
+ static boolean m_fileSystemIsDOS = "\\".equals(File.separator);\r
+ static boolean m_fileSystemIsMac = ":".equals(File.separator);\r
+\r
+ public static boolean fileSystemIsDOS(){return m_fileSystemIsDOS;}\r
+ public static boolean fileSystemIsMac(){return m_fileSystemIsMac;}\r
+\r
+ public static String fixFilename(String filename){\r
+ if ( m_fileSystemIsDOS ) {\r
+ return filename.replace('/', '\\');\r
+ }\r
+ if ( m_fileSystemIsMac ) {\r
+ String t = filename.replace('/', ':');\r
+ t = t.replace('\\', ':');\r
+ return t;\r
+ }\r
+ return filename.replace('\\','/');\r
+ }\r
+\r
+ public static String join(String dir, String file){\r
+ if ( dir.length() == 0 ) {\r
+ return file;\r
+ }\r
+ dir = Tools.fixFilename(dir);\r
+ file = Tools.fixFilename(file);\r
+ if ( ! dir.endsWith(File.separator) ) {\r
+ dir += File.separator;\r
+ }\r
+ if ( file.startsWith(File.separator) ) {\r
+ file = file.substring(1);\r
+ }\r
+ return dir + file;\r
+ }\r
+\r
+\r
+\r
+}\r
--- /dev/null
+package org.collectionspace.services.IntegrationTests.xmlreplay;\r
+\r
+import org.apache.commons.cli.*;\r
+\r
+import org.apache.commons.jexl2.JexlContext;\r
+import org.apache.commons.jexl2.JexlEngine;\r
+import org.apache.commons.jexl2.MapContext;\r
+import org.dom4j.Document;\r
+import org.dom4j.DocumentException;\r
+import org.dom4j.Node;\r
+import org.dom4j.io.SAXReader;\r
+\r
+import java.io.*;\r
+import java.util.*;\r
+\r
+/** This class is used to replay a request to the Services layer, by sending the XML payload\r
+ * in an appropriate Multipart request.\r
+ * See example usage in calling class XmlReplayTest in services/IntegrationTests, and also in main() in this class.\r
+ * @author Laramie Crocker\r
+ */\r
+public class XmlReplay {\r
+\r
+ public XmlReplay(String basedir){\r
+ this.basedir = basedir;\r
+ this.serviceResultsMap = createResultsMap();\r
+ }\r
+\r
+ public static final String DEFAULT_CONTROL = "xml-replay-control.xml";\r
+ public static final String DEFAULT_MASTER_CONTROL = "xml-replay-master.xml";\r
+\r
+ private String basedir = "."; //set from constructor.\r
+ public String getBaseDir(){\r
+ return basedir;\r
+ }\r
+ \r
+ private String controlFileName = DEFAULT_CONTROL;\r
+ public String getControlFileName() {\r
+ return controlFileName;\r
+ }\r
+ public void setControlFileName(String controlFileName) {\r
+ this.controlFileName = controlFileName;\r
+ }\r
+\r
+ private String protoHostPort = "";\r
+ public String getProtoHostPort() {\r
+ return protoHostPort;\r
+ }\r
+ public void setProtoHostPort(String protoHostPort) {\r
+ this.protoHostPort = protoHostPort;\r
+ }\r
+\r
+ private boolean autoDeletePOSTS = true;\r
+ public boolean isAutoDeletePOSTS() {\r
+ return autoDeletePOSTS;\r
+ }\r
+ public void setAutoDeletePOSTS(boolean autoDeletePOSTS) {\r
+ this.autoDeletePOSTS = autoDeletePOSTS;\r
+ }\r
+\r
+ private Dump dump;\r
+ public Dump getDump() {\r
+ return dump;\r
+ }\r
+ public void setDump(Dump dump) {\r
+ this.dump = dump;\r
+ }\r
+\r
+ AuthsMap defaultAuthsMap;\r
+ public AuthsMap getDefaultAuthsMap(){\r
+ return defaultAuthsMap;\r
+ }\r
+ public void setDefaultAuthsMap(AuthsMap authsMap){\r
+ defaultAuthsMap = authsMap;\r
+ }\r
+\r
+ private Map<String, ServiceResult> serviceResultsMap;\r
+ public Map<String, ServiceResult> getServiceResultsMap(){\r
+ return serviceResultsMap;\r
+ }\r
+ public static Map<String, ServiceResult> createResultsMap(){\r
+ return new HashMap<String, ServiceResult>();\r
+ }\r
+\r
+\r
+ public String toString(){\r
+ return "XmlReplay{"+this.basedir+", "+this.controlFileName+", "+this.defaultAuthsMap+", "+this.dump+'}';\r
+ }\r
+\r
+ // ============== METHODS ===========================================================\r
+\r
+ public Document openMasterConfigFile(String masterFilename) throws FileNotFoundException {\r
+ Document document = getDocument(Tools.glue(basedir, "/", masterFilename)); //will check full path first, then checks relative to PWD.\r
+ if (document == null){\r
+ throw new FileNotFoundException("XmlReplay master control file ("+masterFilename+") not found in basedir: "+basedir+". Exiting test.");\r
+ }\r
+ return document;\r
+ }\r
+\r
+ /** specify the master config file, relative to getBaseDir(), but ignore any tests or testGroups in the master.\r
+ * @return a Document object, which you don't need to use: all options will be stored in XmlReplay instance.\r
+ */\r
+ public Document readOptionsFromMasterConfigFile(String masterFilename) throws FileNotFoundException {\r
+ Document document = openMasterConfigFile(masterFilename);\r
+ protoHostPort = document.selectSingleNode("/xmlReplayMaster/protoHostPort").getText().trim();\r
+ AuthsMap authsMap = readAuths(document);\r
+ setDefaultAuthsMap(authsMap);\r
+ Dump dump = XmlReplay.readDumpOptions(document);\r
+ setDump(dump);\r
+ return document;\r
+ }\r
+\r
+ public List<List<ServiceResult>> runMaster(String masterFilename) throws Exception {\r
+ return runMaster(masterFilename, true);\r
+ }\r
+\r
+ /** Creates new instances of XmlReplay, one for each controlFile specified in the master,\r
+ * and setting defaults from this instance, but not sharing ServiceResult objects or maps. */\r
+ public List<List<ServiceResult>> runMaster(String masterFilename, boolean readOptionsFromMaster) throws Exception {\r
+ List<List<ServiceResult>> list = new ArrayList<List<ServiceResult>>();\r
+ Document document;\r
+ if (readOptionsFromMaster){\r
+ document = readOptionsFromMasterConfigFile(masterFilename);\r
+ } else {\r
+ document = openMasterConfigFile(masterFilename);\r
+ }\r
+ String controlFile, testGroup, test;\r
+ List<Node> runNodes;\r
+ runNodes = document.selectNodes("/xmlReplayMaster/run");\r
+ for (Node runNode : runNodes) {\r
+ controlFile = runNode.valueOf("@controlFile");\r
+ testGroup = runNode.valueOf("@testGroup");\r
+ test = runNode.valueOf("@test"); //may be empty\r
+\r
+ //Create a new instance and clone only config values, not any results maps.\r
+ XmlReplay replay = new XmlReplay(basedir);\r
+ replay.setControlFileName(controlFile);\r
+ replay.setProtoHostPort(protoHostPort);\r
+ replay.setAutoDeletePOSTS(isAutoDeletePOSTS());\r
+ replay.setDump(dump);\r
+ replay.setDefaultAuthsMap(getDefaultAuthsMap());\r
+\r
+ //Now run *that* instance.\r
+ List<ServiceResult> results = replay.runTests(testGroup, test);\r
+ list.add(results);\r
+ }\r
+ return list;\r
+ }\r
+\r
+ /** Use this if you wish to named tests within a testGroup, otherwise call runTestGroup(). */\r
+ public List<ServiceResult> runTests(String testGroupID, String testID) throws Exception {\r
+ List<ServiceResult> result = runXmlReplayFile(this.basedir,\r
+ this.controlFileName,\r
+ testGroupID,\r
+ testID,\r
+ this.serviceResultsMap,\r
+ this.autoDeletePOSTS,\r
+ dump,\r
+ this.protoHostPort,\r
+ this.defaultAuthsMap);\r
+ return result;\r
+ }\r
+\r
+ /** Use this if you wish to specify just ONE test to run within a testGroup, otherwise call runTestGroup(). */\r
+ public ServiceResult runTest(String testGroupID, String testID) throws Exception {\r
+ List<ServiceResult> result = runXmlReplayFile(this.basedir,\r
+ this.controlFileName,\r
+ testGroupID,\r
+ testID,\r
+ this.serviceResultsMap,\r
+ this.autoDeletePOSTS,\r
+ dump,\r
+ this.protoHostPort,\r
+ this.defaultAuthsMap);\r
+ if (result.size()>1){\r
+ throw new IndexOutOfBoundsException("Multiple ("+result.size()+") tests with ID='"+testID+"' were found within test group '"+testGroupID+"', but there should only be one test per ID attribute.");\r
+ }\r
+ return result.get(0);\r
+ }\r
+\r
+ /** Use this if you wish to run all tests within a testGroup.*/\r
+ public List<ServiceResult> runTestGroup(String testGroupID) throws Exception {\r
+ //NOTE: calling runTest with empty testID runs all tests in a test group, but don't expose this fact.\r
+ // Expose this method (runTestGroup) instead.\r
+ return runTests(testGroupID, "");\r
+ }\r
+\r
+ public List<ServiceResult> autoDelete(String logName){\r
+ return autoDelete(this.serviceResultsMap, logName);\r
+ }\r
+\r
+ /** Use this method to clean up resources created on the server that returned CSIDs, if you have\r
+ * specified autoDeletePOSTS==false, which means you are managing the cleanup yourself.\r
+ * @param serviceResultsMap a Map of ServiceResult objects, which will contain ServiceResult.deleteURL.\r
+ * @return a List<String> of debug info about which URLs could not be deleted.\r
+ */\r
+ public static List<ServiceResult> autoDelete(Map<String, ServiceResult> serviceResultsMap, String logName){\r
+ List<ServiceResult> results = new ArrayList<ServiceResult>();\r
+ for (ServiceResult pr : serviceResultsMap.values()){\r
+ try {\r
+ ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, pr.auth, pr.testID, "[autodelete:"+logName+"]");\r
+ results.add(deleteResult);\r
+ } catch (Throwable t){\r
+ String s = (pr!=null) ? "ERROR while cleaning up ServiceResult map: "+pr+" for "+pr.deleteURL+" :: "+t\r
+ : "ERROR while cleaning up ServiceResult map (null ServiceResult): "+t;\r
+ System.err.println(s);\r
+ ServiceResult errorResult = new ServiceResult();\r
+ errorResult.fullURL = pr.fullURL;\r
+ errorResult.testGroupID = pr.testGroupID;\r
+ errorResult.fromTestID = pr.fromTestID;\r
+ errorResult.error = s;\r
+ results.add(errorResult);\r
+ }\r
+ }\r
+ return results;\r
+ }\r
+\r
+ public static class AuthsMap {\r
+ Map<String,String> map;\r
+ String defaultID="";\r
+ public String getDefaultAuth(){\r
+ return map.get(defaultID);\r
+ }\r
+ public String toString(){\r
+ return "AuthsMap: {default='"+defaultID+"'; "+map.keySet()+'}';\r
+ }\r
+ }\r
+\r
+ public static AuthsMap readAuths(Document document){\r
+ Map<String, String> map = new HashMap<String, String>();\r
+ List<Node> authNodes = document.selectNodes("//auths/auth");\r
+ for (Node auth : authNodes) {\r
+ map.put(auth.valueOf("@ID"), auth.getStringValue());\r
+ }\r
+ AuthsMap authsMap = new AuthsMap();\r
+ Node auths = document.selectSingleNode("//auths");\r
+ String defaultID = "";\r
+ if (auths != null){\r
+ defaultID = auths.valueOf("@default");\r
+ }\r
+ authsMap.map = map;\r
+ authsMap.defaultID = defaultID;\r
+ return authsMap;\r
+ }\r
+\r
+ public static class Dump {\r
+ public boolean payloads = false;\r
+ //public static final ServiceResult.DUMP_OPTIONS dumpServiceResultOptions = ServiceResult.DUMP_OPTIONS;\r
+ public ServiceResult.DUMP_OPTIONS dumpServiceResult = ServiceResult.DUMP_OPTIONS.minimal;\r
+ public String toString(){\r
+ return "payloads: "+payloads+" dumpServiceResult: "+dumpServiceResult;\r
+ }\r
+ }\r
+\r
+ public static Dump getDumpConfig(){\r
+ return new Dump();\r
+ }\r
+\r
+ public static Dump readDumpOptions(Document document){\r
+ Dump dump = getDumpConfig();\r
+ Node dumpNode = document.selectSingleNode("//dump");\r
+ if (dumpNode != null){\r
+ dump.payloads = Tools.isTrue(dumpNode.valueOf("@payloads"));\r
+ String dumpServiceResultStr = dumpNode.valueOf("@dumpServiceResult");\r
+ if (Tools.notEmpty(dumpServiceResultStr)){\r
+ dump.dumpServiceResult = ServiceResult.DUMP_OPTIONS.valueOf(dumpServiceResultStr);\r
+ }\r
+ }\r
+ return dump;\r
+ }\r
+\r
+ private static class PartsStruct {\r
+ public List<String> partsList = new ArrayList<String>();\r
+ public List<String> filesList = new ArrayList<String>();\r
+ boolean bDoingSinglePartPayload = false;\r
+ String singlePartPayloadFilename = "";\r
+ String overrideTestID = "";\r
+ public static PartsStruct readParts(Node testNode, final String testID, String xmlReplayBaseDir){\r
+ PartsStruct result = new PartsStruct();\r
+ result.singlePartPayloadFilename = testNode.valueOf("filename");\r
+ String singlePartPayloadFilename = testNode.valueOf("filename");\r
+ if (Tools.notEmpty(singlePartPayloadFilename)){\r
+ result.bDoingSinglePartPayload = true;\r
+ result.singlePartPayloadFilename = xmlReplayBaseDir + '/' + singlePartPayloadFilename;\r
+ } else {\r
+ result.bDoingSinglePartPayload = false;\r
+ List<Node> parts = testNode.selectNodes("parts/part");\r
+ if (parts == null || parts.size()==0){ //path is just /testGroup/test/part/\r
+ String commonPartName = testNode.valueOf("part/label");\r
+ String testfile = testNode.valueOf("part/filename");\r
+ String fullTestFilename = xmlReplayBaseDir + '/' + testfile;\r
+ if ( Tools.isEmpty(testID) ){\r
+ result.overrideTestID = testfile; //It is legal to have a missing ID attribute, and rely on a unique filename.\r
+ }\r
+ result.partsList.add(commonPartName);\r
+ result.filesList.add(fullTestFilename);\r
+ } else { // path is /testGroup/test/parts/part/\r
+ for (Node part : parts){\r
+ String commonPartName = part.valueOf("label");\r
+ String filename = part.valueOf("filename");\r
+ String fullTestFilename = xmlReplayBaseDir + '/' + filename;\r
+ if ( Tools.isEmpty(testID) ){ //if testID is empty, we'll use the *first* filename as ID.\r
+ result.overrideTestID = filename; //It is legal to have a missing ID attribute, and rely on a unique filename.\r
+ }\r
+ result.partsList.add(commonPartName);\r
+ result.filesList.add(fullTestFilename);\r
+ }\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+ }\r
+\r
+ private static String fixupFullURL(String fullURL, String protoHostPort, String uri){\r
+ if ( ! uri.startsWith(protoHostPort)){\r
+ fullURL = Tools.glue(protoHostPort, "/", uri);\r
+ } else {\r
+ fullURL = uri;\r
+ }\r
+ return fullURL;\r
+ }\r
+\r
+ private static String fromTestID(String fullURL, Node testNode, Map<String, ServiceResult> serviceResultsMap){\r
+ String fromTestID = testNode.valueOf("fromTestID");\r
+ if (Tools.notEmpty(fromTestID)){\r
+ ServiceResult getPR = serviceResultsMap.get(fromTestID);\r
+ if (getPR != null){\r
+ fullURL = Tools.glue(fullURL, "/", getPR.location);\r
+ }\r
+ }\r
+ return fullURL;\r
+ }\r
+\r
+ private static String CSIDfromTestID(Node testNode, Map<String, ServiceResult> serviceResultsMap){\r
+ String result = "";\r
+ String fromTestID = testNode.valueOf("fromTestID");\r
+ if (Tools.notEmpty(fromTestID)){\r
+ ServiceResult getPR = serviceResultsMap.get(fromTestID);\r
+ if (getPR != null){\r
+ result = getPR.location;\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+\r
+\r
+ public static org.dom4j.Document getDocument(String xmlFileName) {\r
+ Document document = null;\r
+ SAXReader reader = new SAXReader();\r
+ try {\r
+ document = reader.read(xmlFileName);\r
+ } catch (DocumentException e) {\r
+ //e.printStackTrace();\r
+ }\r
+ return document;\r
+ }\r
+\r
+\r
+ //================= runXmlReplayFile ======================================================\r
+\r
+ public static List<ServiceResult> runXmlReplayFile(String xmlReplayBaseDir,\r
+ String controlFileName,\r
+ String testGroupID,\r
+ String oneTestID,\r
+ Map<String, ServiceResult> serviceResultsMap,\r
+ boolean param_autoDeletePOSTS,\r
+ Dump dump,\r
+ String protoHostPortParam,\r
+ AuthsMap defaultAuths)\r
+ throws Exception {\r
+ //Internally, we maintain two collections of ServiceResult:\r
+ // the first is the return value of this method.\r
+ // the second is the serviceResultsMap, which is used for keeping track of CSIDs created by POSTs, for later reference by DELETE, etc.\r
+ List<ServiceResult> results = new ArrayList<ServiceResult>();\r
+\r
+ String controlFile = Tools.glue(xmlReplayBaseDir, "/", controlFileName);\r
+ Document document;\r
+ document = getDocument(controlFile); //will check full path first, then checks relative to PWD.\r
+ if (document==null){\r
+ throw new FileNotFoundException("XmlReplay control file ("+controlFileName+") not found in basedir: "+xmlReplayBaseDir+" Exiting test.");\r
+ }\r
+ String protoHostPort;\r
+ if (Tools.isEmpty(protoHostPortParam)){\r
+ protoHostPort = document.selectSingleNode("/xmlReplay/protoHostPort").getText().trim();\r
+ System.out.println("DEPRECATED: Using protoHostPort ('"+protoHostPort+"') from xmlReplay file ('"+controlFile+"'), not master.");\r
+ } else {\r
+ protoHostPort = protoHostPortParam;\r
+ }\r
+ if (Tools.isEmpty(protoHostPort)){\r
+ throw new Exception("XmlReplay control file must have a protoHostPort element");\r
+ }\r
+\r
+ String authsMapINFO;\r
+ AuthsMap authsMap = readAuths(document);\r
+ if (authsMap.map.size()==0){\r
+ authsMap = defaultAuths;\r
+ authsMapINFO = "Using defaultAuths from master file: "+defaultAuths;\r
+ } else {\r
+ authsMapINFO = "Using AuthsMap from control file: "+authsMap;\r
+ }\r
+ System.out.println("XmlReplay running:"\r
+ +"\r\n controlFile: "+ (new File(controlFile).getCanonicalPath())\r
+ +"\r\n protoHostPort: "+protoHostPort\r
+ +"\r\n testGroup: "+testGroupID\r
+ + (Tools.notEmpty(oneTestID) ? "\r\n oneTestID: "+oneTestID : "")\r
+ +"\r\n AuthsMap: "+authsMapINFO\r
+ +"\r\n param_autoDeletePOSTS: "+param_autoDeletePOSTS\r
+ +"\r\n Dump info: "+dump\r
+ +"\r\n");\r
+\r
+ String autoDeletePOSTS = "";\r
+ List<Node> testgroupNodes;\r
+ if (Tools.notEmpty(testGroupID)){\r
+ testgroupNodes = document.selectNodes("//testGroup[@ID='"+testGroupID+"']");\r
+ } else {\r
+ testgroupNodes = document.selectNodes("//testGroup");\r
+ }\r
+\r
+ JexlEngine jexl = new JexlEngine(); // Used for expression language expansion from uri field.\r
+ XmlReplayEval evalStruct = new XmlReplayEval();\r
+ evalStruct.serviceResultsMap = serviceResultsMap;\r
+ evalStruct.jexl = jexl;\r
+\r
+ for (Node testgroup : testgroupNodes) {\r
+ JexlContext jc = new MapContext(); //Get a new JexlContext for each test group.\r
+ evalStruct.jc = jc;\r
+\r
+ autoDeletePOSTS = testgroup.valueOf("@autoDeletePOSTS");\r
+ List<Node> tests;\r
+ if (Tools.notEmpty(oneTestID)){\r
+ tests = testgroup.selectNodes("test[@ID='"+oneTestID+"']");\r
+ } else {\r
+ tests = testgroup.selectNodes("test");\r
+ }\r
+ String authForTest = "";\r
+ int testElementIndex = -1;\r
+\r
+ for (Node testNode : tests) {\r
+ testElementIndex++;\r
+ String testID = testNode.valueOf("@ID");\r
+ String testIDLabel = Tools.notEmpty(testID) ? (testGroupID+'.'+testID) : (testGroupID+'.'+testElementIndex);\r
+ String method = testNode.valueOf("method");\r
+ String uri = testNode.valueOf("uri");\r
+ String fullURL = Tools.glue(protoHostPort, "/", uri);\r
+ String initURI = uri;\r
+\r
+ String authIDForTest = testNode.valueOf("@auth");\r
+ String currentAuthForTest = authsMap.map.get(authIDForTest);\r
+ if (Tools.notEmpty(currentAuthForTest)){\r
+ authForTest = currentAuthForTest; //else just run with current from last loop;\r
+ }\r
+ if (Tools.isEmpty(authForTest)){\r
+ authForTest = defaultAuths.getDefaultAuth();\r
+ }\r
+\r
+ if (uri.indexOf("$")>-1){\r
+ uri = evalStruct.eval(uri, serviceResultsMap, jexl, jc);\r
+ }\r
+ fullURL = fixupFullURL(fullURL, protoHostPort, uri);\r
+\r
+ List<Integer> expectedCodes = new ArrayList<Integer>();\r
+ String expectedCodesStr = testNode.valueOf("expectedCodes");\r
+ if (Tools.notEmpty(expectedCodesStr)){\r
+ String[] codesArray = expectedCodesStr.split(",");\r
+ for (String code : codesArray){\r
+ expectedCodes.add(new Integer(code));\r
+ }\r
+ }\r
+\r
+ ServiceResult serviceResult;\r
+ boolean isPOST = method.equalsIgnoreCase("POST");\r
+ boolean isPUT = method.equalsIgnoreCase("PUT");\r
+ if ( isPOST || isPUT ) {\r
+ PartsStruct parts = PartsStruct.readParts(testNode, testID, xmlReplayBaseDir);\r
+ if (Tools.notEmpty(parts.overrideTestID)) {\r
+ testID = parts.overrideTestID;\r
+ }\r
+ if (isPOST){\r
+ String csid = CSIDfromTestID(testNode, serviceResultsMap);\r
+ if (Tools.notEmpty(csid)) uri = Tools.glue(uri, "/", csid+"/items/");\r
+ } else if (isPUT) {\r
+ uri = fromTestID(uri, testNode, serviceResultsMap);\r
+ }\r
+ if (parts.bDoingSinglePartPayload){\r
+ serviceResult = XmlReplayTransport.doPOST_PUTFromXML(parts.singlePartPayloadFilename, protoHostPort, uri, "POST", XmlReplayTransport.APPLICATION_XML, evalStruct, authForTest, testIDLabel);\r
+ } else {\r
+ serviceResult = XmlReplayTransport.doPOST_PUTFromXML_Multipart(parts.filesList, parts.partsList, protoHostPort, uri, "POST", evalStruct, authForTest, testIDLabel);\r
+ }\r
+ results.add(serviceResult);\r
+ if (isPOST){\r
+ serviceResultsMap.put(testID, serviceResult); //PUTs do not return a Location, so don't add PUTs to serviceResultsMap.\r
+ }\r
+ fullURL = fixupFullURL(fullURL, protoHostPort, uri);\r
+ } else if (method.equalsIgnoreCase("DELETE")){\r
+ String fromTestID = testNode.valueOf("fromTestID");\r
+ ServiceResult pr = serviceResultsMap.get(fromTestID);\r
+ if (pr!=null){\r
+ serviceResult = XmlReplayTransport.doDELETE(pr.deleteURL, authForTest, testIDLabel, fromTestID);\r
+ serviceResult.fromTestID = fromTestID;\r
+ results.add(serviceResult);\r
+ if (serviceResult.gotExpectedResult()){\r
+ serviceResultsMap.remove(fromTestID);\r
+ }\r
+ } else {\r
+ if (Tools.notEmpty(fromTestID)){\r
+ serviceResult = new ServiceResult();\r
+ serviceResult.responseCode = 0;\r
+ serviceResult.error = "ID not found in element fromTestID: "+fromTestID;\r
+ System.err.println("****\r\nServiceResult: "+serviceResult.error+". SKIPPING TEST. Full URL: "+fullURL);\r
+ } else {\r
+ serviceResult = XmlReplayTransport.doDELETE(fullURL, authForTest, testID, fromTestID);\r
+ }\r
+ serviceResult.fromTestID = fromTestID;\r
+ results.add(serviceResult);\r
+ }\r
+ } else if (method.equalsIgnoreCase("GET")){\r
+ fullURL = fromTestID(fullURL, testNode, serviceResultsMap);\r
+ serviceResult = XmlReplayTransport.doGET(fullURL, authForTest, testIDLabel);\r
+ results.add(serviceResult);\r
+ } else if (method.equalsIgnoreCase("LIST")){\r
+ fullURL = fixupFullURL(fullURL, protoHostPort, uri);\r
+ String listQueryParams = ""; //TODO: empty for now, later may pick up from XML control file.\r
+ serviceResult = XmlReplayTransport.doLIST(fullURL, listQueryParams, authForTest, testIDLabel);\r
+ results.add(serviceResult);\r
+ } else {\r
+ throw new Exception("HTTP method not supported by XmlReplay: "+method);\r
+ }\r
+\r
+ serviceResult.testID = testID;\r
+ serviceResult.fullURL = fullURL;\r
+ serviceResult.auth = authForTest;\r
+ serviceResult.method = method;\r
+ if (expectedCodes.size()>0){\r
+ serviceResult.expectedCodes = expectedCodes;\r
+ }\r
+ if (Tools.isEmpty(serviceResult.testID)) serviceResult.testID = testIDLabel;\r
+ if (Tools.isEmpty(serviceResult.testGroupID)) serviceResult.testGroupID = testGroupID;\r
+\r
+ String serviceResultRow = serviceResult.dump(dump.dumpServiceResult);\r
+ String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:"+testIDLabel+": ": "";\r
+ System.out.println(leader+serviceResultRow+"\r\n");\r
+ if (dump.payloads) System.out.println(serviceResult.result);\r
+ }\r
+ if (Tools.isTrue(autoDeletePOSTS)&¶m_autoDeletePOSTS){\r
+ autoDelete(serviceResultsMap, "default");\r
+ }\r
+ }\r
+ return results;\r
+ }\r
+\r
+\r
+ //======================== MAIN ===================================================================\r
+\r
+ private static Options createOptions() {\r
+ Options options = new Options();\r
+ options.addOption("xmlReplayBaseDir", true, "default/basedir");\r
+ return options;\r
+ }\r
+\r
+ public static String usage(){\r
+ String result = "org.collectionspace.services.IntegrationTests.xmlreplay.XmlReplay {args}\r\n"\r
+ +" -xmlReplayBaseDir <dir> \r\n"\r
+ +" You may also override these with system args, e.g.: \r\n"\r
+ +" -DxmlReplayBaseDir=/path/to/dir \r\n"\r
+ +" These may also be passed in via the POM.\r\n"\r
+ +" You can also set these system args, e.g.: \r\n"\r
+ +" -DtestGroupID=<oneID> \r\n"\r
+ +" -DtestID=<one TestGroup ID>"\r
+ +" -DautoDeletePOSTS=<true|false> \r\n"\r
+ +" (note: -DautoDeletePOSTS won't force deletion if set to false in control file.";\r
+ return result;\r
+ }\r
+\r
+ private static String opt(CommandLine line, String option){\r
+ String result;\r
+ String fromProps = System.getProperty(option);\r
+ if (Tools.notEmpty(fromProps)){\r
+ return fromProps;\r
+ }\r
+ result = line.getOptionValue(option);\r
+ if (result == null){\r
+ result = "";\r
+ }\r
+ return result;\r
+ }\r
+\r
+ public static void main(String[]args) throws Exception {\r
+ Options options = createOptions();\r
+ //System.out.println("System CLASSPATH: "+prop.getProperty("java.class.path", null));\r
+ CommandLineParser parser = new GnuParser();\r
+ try {\r
+ // parse the command line arguments\r
+ CommandLine line = parser.parse(options, args);\r
+\r
+ String xmlReplayBaseDir = opt(line, "xmlReplayBaseDir");\r
+ String testGroupID = opt(line, "testGroupID");\r
+ String testID = opt(line, "testID");\r
+ String autoDeletePOSTS = opt(line, "autoDeletePOSTS");\r
+ String dumpResults = opt(line, "dumpResults");\r
+ String controlFilename = opt(line, "controlFilename");\r
+ String xmlReplayMaster = opt(line, "xmlReplayMaster");\r
+\r
+ xmlReplayBaseDir = Tools.fixFilename(xmlReplayBaseDir);\r
+ controlFilename = Tools.fixFilename(controlFilename);\r
+\r
+ boolean bAutoDeletePOSTS = true;\r
+ if (Tools.notEmpty(autoDeletePOSTS)) {\r
+ bAutoDeletePOSTS = Tools.isTrue(autoDeletePOSTS);\r
+ }\r
+ boolean bDumpResults = false;\r
+ if (Tools.notEmpty(dumpResults)) {\r
+ bDumpResults = Tools.isTrue(autoDeletePOSTS);\r
+ }\r
+ if (Tools.isEmpty(xmlReplayBaseDir)){\r
+ System.err.println("ERROR: xmlReplayBaseDir was not specified.");\r
+ return;\r
+ }\r
+ File f = new File(Tools.glue(xmlReplayBaseDir, "/", controlFilename));\r
+ if (Tools.isEmpty(xmlReplayMaster) && !f.exists()){\r
+ System.err.println("Control file not found: "+f.getCanonicalPath());\r
+ return;\r
+ }\r
+ File fMaster = new File(Tools.glue(xmlReplayBaseDir, "/", xmlReplayMaster));\r
+ if (Tools.notEmpty(xmlReplayMaster) && !fMaster.exists()){\r
+ System.err.println("Master file not found: "+fMaster.getCanonicalPath());\r
+ return;\r
+ }\r
+\r
+ String xmlReplayBaseDirResolved = (new File(xmlReplayBaseDir)).getCanonicalPath();\r
+ System.out.println("XmlReplay ::"\r
+ + "\r\n xmlReplayBaseDir: "+xmlReplayBaseDir\r
+ + "\r\n xmlReplayBaseDir(resolved): "+xmlReplayBaseDirResolved\r
+ + "\r\n controlFilename: "+controlFilename\r
+ + "\r\n xmlReplayMaster: "+xmlReplayMaster\r
+ + "\r\n testGroupID: "+testGroupID\r
+ + "\r\n testID: "+testID\r
+ + "\r\n autoDeletePOSTS: "+bAutoDeletePOSTS\r
+ + (Tools.notEmpty(xmlReplayMaster)\r
+ ? ("\r\n will use master file: "+fMaster.getCanonicalPath())\r
+ : ("\r\n will use control file: "+f.getCanonicalPath()) )\r
+ );\r
+ \r
+ if (Tools.notEmpty(xmlReplayMaster)){\r
+ if (Tools.notEmpty(controlFilename)){\r
+ System.out.println("WARN: controlFilename: "+controlFilename+" will not be used because master was specified. Running master: "+xmlReplayMaster);\r
+ }\r
+ XmlReplay replay = new XmlReplay(xmlReplayBaseDirResolved);\r
+ replay.readOptionsFromMasterConfigFile(xmlReplayMaster);\r
+ replay.setAutoDeletePOSTS(bAutoDeletePOSTS);\r
+ Dump dumpFromMaster = replay.getDump();\r
+ dumpFromMaster.payloads = Tools.isTrue(dumpResults);\r
+ replay.setDump(dumpFromMaster);\r
+ replay.runMaster(xmlReplayMaster, false); //false, because we already just read the options, and override a few.\r
+ } else {\r
+ Dump dump = getDumpConfig();\r
+ dump.payloads = Tools.isTrue(dumpResults);\r
+ runXmlReplayFile(xmlReplayBaseDirResolved, controlFilename, testGroupID, testID, createResultsMap(), bAutoDeletePOSTS, dump, "", null);\r
+ }\r
+ } catch (ParseException exp) {\r
+ // oops, something went wrong\r
+ System.err.println("Cmd-line parsing failed. Reason: " + exp.getMessage());\r
+ System.err.println(usage());\r
+ } catch (Exception e) {\r
+ System.out.println("Error : " + e.getMessage());\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * This document is a part of the source code and related artifacts\r
+ * for CollectionSpace, an open source collections management system\r
+ * for museums and related institutions:\r
+ *\r
+ * http://www.collectionspace.org\r
+ * http://wiki.collectionspace.org\r
+ *\r
+ * Copyright (c) 2009 Regents of the University of California\r
+ *\r
+ * Licensed under the Educational Community License (ECL), Version 2.0.\r
+ * You may not use this file except in compliance with this License.\r
+ *\r
+ * You may obtain a copy of the ECL 2.0 License at\r
+ * https://source.collectionspace.org/collection-space/LICENSE.txt\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.collectionspace.services.IntegrationTests.xmlreplay;\r
+\r
+import org.apache.commons.jexl2.Expression;\r
+import org.apache.commons.jexl2.JexlContext;\r
+import org.apache.commons.jexl2.JexlEngine;\r
+\r
+import java.util.Map;\r
+\r
+/**\r
+ * User: laramie\r
+ * $LastChangedRevision: $\r
+ * $LastChangedDate: $\r
+ */\r
+public class XmlReplayEval {\r
+ public Map<String, ServiceResult> serviceResultsMap;\r
+ public JexlEngine jexl;\r
+ public JexlContext jc;\r
+\r
+ /**\r
+ * You may pass in a Jexl 2 expression, e.g. ${foo.bar} and it will be eval'd for you.\r
+ * We are looking at some URI like so: ${newOrgAuthority.CSID}\r
+ * The idea here is that the XML control file may bind to this namespace, and\r
+ * this module may find those values and any future extensions, specifically\r
+ * when someone says "I want to bind to ${CSID} and ${SUBRESOURCE.CSID}\r
+ * The code here is easy to extend, but the test cases build up, so you don't\r
+ * want to break all the config files by not being backward compatible. Binding\r
+ * to context variables like this makes it easy.\r
+ * EXAMPLE USAGE: <br />\r
+ * String uri = "/cspace-services/orgauthorities/${OrgAuth1.CSID}/items/${Org1.CSID}"; <br />\r
+ * uri = eval(uri, serviceResultsMap, jexl, jc); <br />\r
+ * RESULT: "/cspace-services/orgauthorities/43a2739c-4f40-49c8-a6d5/items/"\r
+ */\r
+ public static String eval(String inputJexlExpression, Map<String, ServiceResult> serviceResultsMap, JexlEngine jexl, JexlContext jc) {\r
+ //System.out.println("\r\n---- REPLACE.init-uri: "+inputJexlExpression);\r
+ String result;\r
+ try {\r
+ for (ServiceResult postResult : serviceResultsMap.values()) {\r
+ jc.set(postResult.testID, postResult);\r
+ //System.out.println("eval :: "+postResult.testID+"==>"+postResult);\r
+ }\r
+ result = parse(inputJexlExpression, jexl, jc);\r
+ } catch (Throwable t) {\r
+ System.err.println("ERROR: " + t);\r
+ result = "ERROR";\r
+ }\r
+ //System.out.println("---- REPLACE.uri: "+result+"\r\n");\r
+ return result;\r
+ }\r
+\r
+ private static String parse(String in, JexlEngine jexl, JexlContext jc) {\r
+ StringBuffer result = new StringBuffer();\r
+ String s = in;\r
+ String var = "";\r
+ int start, end, len;\r
+ len = in.length();\r
+ start = 0;\r
+ int cursor = 0;\r
+ String front = "";\r
+ while (start < len) {\r
+ end = in.indexOf("}", start);\r
+ start = in.indexOf("${", start);\r
+ if (start < 0) {\r
+ String tail = in.substring(cursor);\r
+ result.append(tail);\r
+ break;\r
+ }\r
+ if (end < 0) {\r
+ return "ERROR: unbalanced ${} braces";\r
+ }\r
+ front = in.substring(cursor, start);\r
+ result.append(front);\r
+ cursor = end + 1; //bump past close brace\r
+ var = in.substring(start + 2, end); //+2 bump past open brace ${ and then "end" is indexed just before the close brace }\r
+ //s = s.substring(end+1); //bump past close brace\r
+ start = cursor;\r
+\r
+ Expression expr = jexl.createExpression(var);\r
+ Object resultObj = expr.evaluate(jc);\r
+ String resultStr;\r
+ if (null == resultObj){\r
+ resultStr = "ERROR";\r
+ System.out.println("Jexl context: "+jc.toString());\r
+ } else {\r
+ resultStr = resultObj.toString();\r
+\r
+ }\r
+ result.append(resultStr);\r
+ }\r
+ return result.toString();\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * This document is a part of the source code and related artifacts\r
+ * for CollectionSpace, an open source collections management system\r
+ * for museums and related institutions:\r
+ *\r
+ * http://www.collectionspace.org\r
+ * http://wiki.collectionspace.org\r
+ *\r
+ * Copyright (c) 2009 Regents of the University of California\r
+ *\r
+ * Licensed under the Educational Community License (ECL), Version 2.0.\r
+ * You may not use this file except in compliance with this License.\r
+ *\r
+ * You may obtain a copy of the ECL 2.0 License at\r
+ * https://source.collectionspace.org/collection-space/LICENSE.txt\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.collectionspace.services.IntegrationTests.xmlreplay;\r
+\r
+import org.testng.Assert;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+/** Subclass this test to programmatically control XmlReplay from a surefire test. See example in IntegrationTests :: XmlReplaySelfTest\r
+ * User: laramie\r
+ * $LastChangedRevision: $\r
+ * $LastChangedDate: $\r
+ */\r
+public class XmlReplayTest {\r
+\r
+ public static final String XMLREPLAY_REL_DIR_TO_MODULE = "/src/test/resources/test-data/xmlreplay";\r
+\r
+ /** To use this method, you should have a test repository of xml files in the path\r
+ * defined by XMLREPLAY_REL_DIR_TO_MODULE, relative to your pom.xml file, but normally\r
+ * you would use the central repository of tests, which live in services/IntegrationTests,\r
+ * and which you can use by calling createXmlReplay() which calls createXmlReplayUsingIntegrationTestsModule() for you.\r
+ */\r
+ public static XmlReplay createXmlReplayForModule() throws Exception {\r
+ String pwd = (new File(".")).getCanonicalPath();\r
+ System.out.println("createXmlReplayForModule.pwd: "+pwd);\r
+ XmlReplay replay = new XmlReplay(pwd+XMLREPLAY_REL_DIR_TO_MODULE);\r
+ System.out.println("XmlReplay: "+replay);\r
+ return replay;\r
+ }\r
+\r
+ /** Use this method if your test xml files are stored in the central repository,\r
+ * which is "services/IntegrationTests" + XMLREPLAY_REL_DIR_TO_MODULE\r
+ */\r
+ public static XmlReplay createXmlReplay() throws Exception {\r
+ return createXmlReplayUsingIntegrationTestsModule("../..");\r
+ }\r
+\r
+ /**\r
+ * @param relToServicesRoot is a Unix-like path from the calling module to the services root,\r
+ * so if if you are in services/dimension/client/\r
+ * then relToServicesRoot is "../.." which is how most of the client tests are set up, or if you\r
+ * are setting up your test repository relative to the main service, e.g. you are in\r
+ * services/dimension/, then relToServicesRoot is ".."\r
+ */\r
+ public static XmlReplay createXmlReplayUsingIntegrationTestsModule(String relToServicesRoot) throws Exception {\r
+ String thisDir = Tools.glue(relToServicesRoot, "/", "IntegrationTests");\r
+ String pwd = (new File(thisDir)).getCanonicalPath();\r
+ System.out.println("createXmlReplayUsingIntegrationTestsModule.pwd: "+pwd);\r
+ XmlReplay replay = new XmlReplay(pwd+XMLREPLAY_REL_DIR_TO_MODULE);\r
+ System.out.println("XmlReplay: "+replay);\r
+ return replay;\r
+ }\r
+\r
+ public static void logTest(ServiceResult sresult, String testname){\r
+ ResultSummary summary = resultSummary(sresult);\r
+ org.testng.Reporter.log(summary.table);\r
+ Assert.assertEquals(summary.oks, summary.total, "Expected all "+summary.total+ " XmlReplay tests to pass. See Output from test '"+testname+"'.");\r
+ }\r
+\r
+ public static void logTest(List<ServiceResult> list, String testname){\r
+ ResultSummary summary = resultSummary(list);\r
+ org.testng.Reporter.log(summary.table);\r
+ Assert.assertEquals(summary.oks, summary.total, "Expected all "+summary.total+ " XmlReplay tests to pass. See Output from test '"+testname+"'.");\r
+ }\r
+\r
+ public static void logTestForGroup(List<List<ServiceResult>> list, String testname){\r
+ ResultSummary summary = resultSummaryForGroup(list);\r
+ org.testng.Reporter.log(summary.table);\r
+ Assert.assertEquals(summary.oks, summary.total, "Expected all "+summary.total+ " XmlReplay tests to pass. See Output from test '"+testname+"'.");\r
+ }\r
+\r
+\r
+ //============== HELPERS AND FORMATTING =====================================================\r
+\r
+ private static final String TBLSTART = "<table border='1'>";\r
+ private static final String ROWSTART = "<tr><td bgcolor='white'>";\r
+ private static final String ROWSTARTRED = "<tr><td bgcolor='red'><b>";\r
+ private static final String SEP = "</td><td>";\r
+ private static final String ROWEND = "</td></tr>";\r
+ private static final String ROWENDRED = "</b></td></tr>";\r
+ private static final String TBLEND = "</table>";\r
+\r
+ public static class ResultSummary {\r
+ public long oks = 0;\r
+ public long total = 0;\r
+ public String table = "";\r
+ public List<String> groups = new ArrayList<String>();\r
+ }\r
+\r
+ public static ResultSummary resultSummaryForGroup(List<List<ServiceResult>> list){\r
+ ResultSummary summary = new ResultSummary();\r
+ summary.oks = 0;\r
+ summary.total = 0;\r
+ StringBuffer buff = new StringBuffer();\r
+ buff.append(TBLSTART);\r
+ for (List<ServiceResult> serviceResults : list){\r
+ String groupID = "";\r
+ if (serviceResults.size()>0){\r
+ groupID = serviceResults.get(0).testGroupID;\r
+ summary.groups.add(groupID);\r
+ }\r
+ buff.append(ROWSTART+"XmlReplay testGroup "+groupID+ROWEND);\r
+ for (ServiceResult serviceResult : serviceResults){\r
+ summary.total++;\r
+ if (serviceResult.gotExpectedResult()){\r
+ summary.oks++;\r
+ buff.append(ROWSTART+serviceResult.minimal()+ROWEND);\r
+ } else {\r
+ buff.append(ROWSTARTRED+serviceResult.minimal()+ROWENDRED);\r
+ }\r
+ }\r
+\r
+ }\r
+ buff.append(TBLEND);\r
+ summary.table = buff.toString();\r
+ return summary;\r
+ }\r
+\r
+ public static ResultSummary resultSummary(List<ServiceResult> serviceResults){\r
+ ResultSummary summary = new ResultSummary();\r
+ summary.oks = 0;\r
+ summary.total = 0;\r
+ StringBuffer buff = new StringBuffer();\r
+ buff.append(TBLSTART);\r
+ for (ServiceResult serviceResult : serviceResults){\r
+ summary.total++;\r
+ if (serviceResult.gotExpectedResult()){\r
+ summary.oks++;\r
+ buff.append(ROWSTART+serviceResult.minimal()+ROWEND);\r
+ } else {\r
+ buff.append(ROWSTARTRED+serviceResult.minimal()+ROWENDRED);\r
+ }\r
+ }\r
+ buff.append(TBLEND);\r
+ summary.table = buff.toString();\r
+ return summary;\r
+ }\r
+\r
+ public static ResultSummary resultSummary(ServiceResult serviceResult){\r
+ ResultSummary summary = new ResultSummary();\r
+ summary.oks = 0;\r
+ summary.total = 1;\r
+ StringBuffer buff = new StringBuffer();\r
+ buff.append(TBLSTART);\r
+ if (serviceResult.gotExpectedResult()){\r
+ summary.oks = 1;\r
+ buff.append(ROWSTART+serviceResult.minimal()+ROWEND);\r
+ } else {\r
+ buff.append(ROWSTARTRED+serviceResult.minimal()+ROWENDRED);\r
+ }\r
+ buff.append(TBLEND);\r
+ summary.table = buff.toString();\r
+ return summary;\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * This document is a part of the source code and related artifacts\r
+ * for CollectionSpace, an open source collections management system\r
+ * for museums and related institutions:\r
+ *\r
+ * http://www.collectionspace.org\r
+ * http://wiki.collectionspace.org\r
+ *\r
+ * Copyright (c) 2009 Regents of the University of California\r
+ *\r
+ * Licensed under the Educational Community License (ECL), Version 2.0.\r
+ * You may not use this file except in compliance with this License.\r
+ *\r
+ * You may obtain a copy of the ECL 2.0 License at\r
+ * https://source.collectionspace.org/collection-space/LICENSE.txt\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.collectionspace.services.IntegrationTests.xmlreplay;\r
+\r
+import org.apache.commons.httpclient.HttpClient;\r
+import org.apache.commons.httpclient.methods.DeleteMethod;\r
+import org.apache.commons.httpclient.methods.GetMethod;\r
+import org.apache.commons.io.FileUtils;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.InputStreamReader;\r
+import java.io.OutputStreamWriter;\r
+import java.net.HttpURLConnection;\r
+import java.net.URL;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import org.collectionspace.services.IntegrationTests.xmlreplay.ServiceResult;\r
+\r
+/**\r
+ * @author Laramie Crocker\r
+ */\r
+public class XmlReplayTransport {\r
+\r
+ private static String BOUNDARY = "34d97c83-0d61-4958-80ab-6bf8d362290f";\r
+ private static String DD = "--";\r
+ private static String CRLF = "\r\n";\r
+\r
+ public static ServiceResult doGET(String urlString, String authForTest, String fromTestID) throws Exception {\r
+ HttpClient client = new HttpClient();\r
+ GetMethod getMethod = new GetMethod(urlString);\r
+ getMethod.addRequestHeader("Accept", "multipart/mixed");\r
+ getMethod.addRequestHeader("Accept", "application/xml");\r
+ getMethod.setRequestHeader("Authorization", "Basic " + authForTest); //"dGVzdDp0ZXN0");\r
+ getMethod.setRequestHeader("X-XmlReplay-fromTestID", fromTestID);\r
+ ServiceResult pr = new ServiceResult();\r
+\r
+ int statusCode1 = client.executeMethod(getMethod);\r
+ pr.responseCode = statusCode1;\r
+ pr.method = "GET";\r
+ try {\r
+ pr.result = getMethod.getResponseBodyAsString();\r
+ pr.responseMessage = getMethod.getStatusText();\r
+ } catch (Throwable t){\r
+ //System.err.println("ERROR getting content from response: "+t);\r
+ pr.error = t.toString();\r
+ }\r
+\r
+\r
+ getMethod.releaseConnection();\r
+ return pr;\r
+ }\r
+\r
+ public static ServiceResult doDELETE(String urlString, String authForTest, String testID, String fromTestID) throws Exception {\r
+ ServiceResult pr = new ServiceResult();\r
+ pr.method = "DELETE";\r
+ pr.fullURL = urlString;\r
+ if (Tools.isEmpty(urlString)){\r
+ pr.error = "url was empty. Check the result for fromTestID: "+fromTestID+". currentTest: "+testID;\r
+ return pr;\r
+ }\r
+ HttpClient client = new HttpClient();\r
+ DeleteMethod deleteMethod = new DeleteMethod(urlString);\r
+ deleteMethod.setRequestHeader("Accept", "multipart/mixed");\r
+ deleteMethod.addRequestHeader("Accept", "application/xml");\r
+ deleteMethod.setRequestHeader("Authorization", "Basic " + authForTest);\r
+ deleteMethod.setRequestHeader("X-XmlReplay-fromTestID", fromTestID);\r
+ int statusCode1 = 0;\r
+ String res = "";\r
+ try {\r
+ statusCode1 = client.executeMethod(deleteMethod);\r
+ pr.responseCode = statusCode1;\r
+ //System.out.println("statusCode: "+statusCode1+" statusLine ==>" + deleteMethod.getStatusLine());\r
+ pr.responseMessage = deleteMethod.getStatusText();\r
+ res = deleteMethod.getResponseBodyAsString();\r
+ deleteMethod.releaseConnection();\r
+ } catch (Throwable t){\r
+ pr.error = t.toString();\r
+ }\r
+ pr.result = res;\r
+ pr.responseCode = statusCode1;\r
+ return pr;\r
+ }\r
+\r
+ public static ServiceResult doLIST(String urlString, String listQueryParams, String authForTest, String fromTestID) throws Exception {\r
+ //String u = Tools.glue(urlString, "/", "items/");\r
+ if (Tools.notEmpty(listQueryParams)){\r
+ urlString = Tools.glue(urlString, "?", listQueryParams);\r
+ }\r
+ return doGET(urlString, authForTest, fromTestID);\r
+ }\r
+\r
+ public static final String MULTIPART_MIXED = "multipart/mixed";\r
+ public static final String APPLICATION_XML = "application/xml";\r
+\r
+ /** Use this overload for multipart messages. */\r
+ public static ServiceResult doPOST_PUTFromXML_Multipart(List<String> filesList,\r
+ List<String> partsList,\r
+ String protoHostPort,\r
+ String uri,\r
+ String method,\r
+ XmlReplayEval evalStruct,\r
+ String authForTest,\r
+ String fromTestID)\r
+ throws Exception {\r
+ if ( filesList==null||filesList.size()==0\r
+ ||partsList==null||partsList.size()==0\r
+ ||(partsList.size() != filesList.size())){\r
+ throw new Exception("filesList and partsList must not be empty and must have the same number of items each.");\r
+ }\r
+ String content = DD + BOUNDARY;\r
+\r
+ for (int i=0; i<partsList.size(); i++){\r
+ String fileName = filesList.get(i);\r
+ String commonPartName = partsList.get(i);\r
+ byte[] b = FileUtils.readFileToByteArray(new File(fileName));\r
+ String xmlString = new String(b);\r
+\r
+ xmlString = evalStruct.eval(xmlString, evalStruct.serviceResultsMap, evalStruct.jexl, evalStruct.jc);\r
+\r
+ content = content + CRLF + "label: "+commonPartName + CRLF\r
+ + "Content-Type: application/xml" + CRLF\r
+ + CRLF\r
+ + xmlString + CRLF\r
+ + DD + BOUNDARY;\r
+ }\r
+ content = content + DD;\r
+ String urlString = protoHostPort+uri;\r
+ return doPOST_PUT(urlString, content, BOUNDARY, method, MULTIPART_MIXED, authForTest, fromTestID); //method is POST or PUT.\r
+ }\r
+\r
+ /** Use this overload for NON-multipart messages, that is, regular POSTs. */\r
+ public static ServiceResult doPOST_PUTFromXML(String fileName,\r
+ String protoHostPort,\r
+ String uri,\r
+ String method,\r
+ String contentType,\r
+ XmlReplayEval evalStruct,\r
+ String authForTest,\r
+ String fromTestID)\r
+ throws Exception {\r
+ byte[] b = FileUtils.readFileToByteArray(new File(fileName));\r
+ String xmlString = new String(b);\r
+ xmlString = evalStruct.eval(xmlString, evalStruct.serviceResultsMap, evalStruct.jexl, evalStruct.jc);\r
+ String urlString = protoHostPort+uri;\r
+ return doPOST_PUT(urlString, xmlString, BOUNDARY, method, contentType, authForTest, fromTestID); //method is POST or PUT.\r
+ }\r
+\r
+\r
+ public static ServiceResult doPOST_PUT(String urlString, String content, String boundary, String method, String contentType,\r
+ String authForTest, String fromTestID) throws Exception {\r
+ URL url = new URL(urlString);\r
+ HttpURLConnection conn;\r
+ conn = (HttpURLConnection) url.openConnection();\r
+\r
+ if (MULTIPART_MIXED.equalsIgnoreCase(contentType)){\r
+ conn.setRequestProperty("Accept", "multipart/mixed");\r
+ conn.setRequestProperty("content-type", "multipart/mixed; boundary=" + boundary);\r
+ } else {\r
+ conn.setRequestProperty("Accept", "application/xml");\r
+ conn.setRequestProperty("content-type", contentType);\r
+ }\r
+ conn.setRequestProperty("Authorization", "Basic " + authForTest); //TODO: remove test user : hard-coded as "dGVzdDp0ZXN0"\r
+ conn.setRequestProperty("Connection", "close");\r
+ conn.setRequestProperty("X-XmlReplay-fromTestID", fromTestID);\r
+ conn.setDoOutput(true);\r
+ conn.setDoInput(true);\r
+ conn.setRequestMethod(method); // "POST" or "PUT"\r
+ OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());\r
+ wr.write(content);\r
+ wr.flush();\r
+\r
+ ServiceResult result = new ServiceResult();\r
+ try {\r
+ result.responseCode = conn.getResponseCode();\r
+ //System.out.println("responseCode: "+result.responseCode);\r
+ if (400 <= result.responseCode && result.responseCode <= 499){\r
+ return result;\r
+ }\r
+ BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));\r
+ String line;\r
+ StringBuffer sb = new StringBuffer();\r
+ while ((line = rd.readLine()) != null) {\r
+ sb.append(line).append("\r\n");\r
+ }\r
+ String msg = sb.toString();\r
+ result.result = msg;\r
+ rd.close();\r
+ } catch (Throwable t){\r
+ //System.err.println("ERROR getting content from response: "+t);\r
+ result.error = t.toString();\r
+ }\r
+ wr.close();\r
+\r
+\r
+ String deleteURL = "";\r
+ String location = "";\r
+ Map<String, List<String>> headers = conn.getHeaderFields();\r
+ List<String> locations = headers.get("Location");\r
+ if (locations != null){\r
+ String locationZero = locations.get(0);\r
+ if (locationZero != null){\r
+ String[] segments = locationZero.split("/");\r
+ location = segments[segments.length - 1];\r
+ deleteURL = Tools.glue(urlString, "/", location);\r
+ }\r
+ }\r
+ result.location = location;\r
+ result.deleteURL = deleteURL;\r
+ result.CSID = location;\r
+ result.method = method;\r
+ return result;\r
+ }\r
+\r
+}\r