1 package org.collectionspace.services.IntegrationTests.xmlreplay;
3 import org.apache.commons.cli.*;
5 import org.apache.commons.io.FileUtils;
6 import org.apache.commons.jexl2.JexlEngine;
7 import org.collectionspace.services.common.api.Tools;
9 import org.dom4j.io.SAXReader;
14 /** This class is used to replay a request to the Services layer, by sending the XML payload
15 * in an appropriate Multipart request.
16 * See example usage in calling class XmlReplayTest in services/IntegrationTests, and also in main() in this class.
17 * @author Laramie Crocker
19 public class XmlReplay {
21 public XmlReplay(String basedir, String reportsDir){
22 this.basedir = basedir;
23 this.serviceResultsMap = createResultsMap();
24 this.reportsList = new ArrayList<String>();
25 this.reportsDir = reportsDir;
28 public static final String DEFAULT_CONTROL = "xml-replay-control.xml";
29 public static final String DEFAULT_MASTER_CONTROL = "xml-replay-master.xml";
30 public static final String DEFAULT_DEV_MASTER_CONTROL = "dev-master.xml";
31 private static final int MAX_REATTEMPTS = 10;
32 private static final String REATTEMPT_KEY = "REATTEMPT_KEY";
34 private String reportsDir = "";
35 public String getReportsDir(){
38 private String basedir = "."; //set from constructor.
39 public String getBaseDir(){
43 private String controlFileName = DEFAULT_CONTROL;
44 public String getControlFileName() {
45 return controlFileName;
47 public void setControlFileName(String controlFileName) {
48 this.controlFileName = controlFileName;
51 private String protoHostPort = "";
52 public String getProtoHostPort() {
55 public void setProtoHostPort(String protoHostPort) {
56 this.protoHostPort = protoHostPort;
59 private boolean autoDeletePOSTS = true;
60 public boolean isAutoDeletePOSTS() {
61 return autoDeletePOSTS;
63 public void setAutoDeletePOSTS(boolean autoDeletePOSTS) {
64 this.autoDeletePOSTS = autoDeletePOSTS;
68 public Dump getDump() {
71 public void setDump(Dump dump) {
75 AuthsMap defaultAuthsMap;
76 public AuthsMap getDefaultAuthsMap(){
77 return defaultAuthsMap;
79 public void setDefaultAuthsMap(AuthsMap authsMap){
80 defaultAuthsMap = authsMap;
83 private Map<String, ServiceResult> serviceResultsMap;
84 public Map<String, ServiceResult> getServiceResultsMap(){
85 return serviceResultsMap;
87 public static Map<String, ServiceResult> createResultsMap(){
88 return new HashMap<String, ServiceResult>();
91 private List<String> reportsList;
92 public List<String> getReportsList(){
96 public String toString(){
97 return "XmlReplay{"+this.basedir+", "+this.defaultAuthsMap+", "+this.dump+", "+this.reportsDir+'}';
100 // ============== METHODS ===========================================================
102 /** Optional information method: call this method after instantiating this class using the constructor XmlReplay(String), which sets the basedir. Then you
103 * pass in your relative masterFilename to that basedir to this method, which will return true if the file is readable, valid xml, etc.
104 * Do this in preference to just seeing if File.exists(), because there are rules to finding the file relative to the maven test dir, yada, yada.
105 * This method makes it easy to have a development test file that you don't check in, so that dev tests can be missing gracefully, etc.
107 public boolean masterConfigFileExists(String masterFilename){
109 org.dom4j.Document doc = openMasterConfigFile(masterFilename);
114 } catch (Throwable t){
119 public org.dom4j.Document openMasterConfigFile(String masterFilename) throws FileNotFoundException {
120 String fullPath = Tools.glue(basedir, "/", masterFilename);
121 File f = new File(fullPath);
125 org.dom4j.Document document = getDocument(fullPath); //will check full path first, then checks relative to PWD.
126 if (document == null){
127 throw new FileNotFoundException("XmlReplay master control file ("+masterFilename+") not found in basedir: "+basedir+". Exiting test.");
132 /** specify the master config file, relative to getBaseDir(), but ignore any tests or testGroups in the master.
133 * @return a Document object, which you don't need to use: all options will be stored in XmlReplay instance.
135 public org.dom4j.Document readOptionsFromMasterConfigFile(String masterFilename) throws FileNotFoundException {
136 org.dom4j.Document document = openMasterConfigFile(masterFilename);
137 if (document == null){
138 throw new FileNotFoundException(masterFilename);
140 protoHostPort = document.selectSingleNode("/xmlReplayMaster/protoHostPort").getText().trim();
141 AuthsMap authsMap = readAuths(document);
142 setDefaultAuthsMap(authsMap);
143 Dump dump = XmlReplay.readDumpOptions(document);
148 public List<List<ServiceResult>> runMaster(String masterFilename) throws Exception {
149 return runMaster(masterFilename, true);
152 /** Creates new instances of XmlReplay, one for each controlFile specified in the master,
153 * and setting defaults from this instance, but not sharing ServiceResult objects or maps. */
154 public List<List<ServiceResult>> runMaster(String masterFilename, boolean readOptionsFromMaster) throws Exception {
155 List<List<ServiceResult>> list = new ArrayList<List<ServiceResult>>();
156 org.dom4j.Document document;
157 if (readOptionsFromMaster){
158 document = readOptionsFromMasterConfigFile(masterFilename);
160 document = openMasterConfigFile(masterFilename);
163 throw new FileNotFoundException(masterFilename);
165 String controlFile, testGroup, test;
167 runNodes = document.selectNodes("/xmlReplayMaster/run");
168 for (Node runNode : runNodes) {
169 controlFile = runNode.valueOf("@controlFile");
170 testGroup = runNode.valueOf("@testGroup");
171 test = runNode.valueOf("@test"); //may be empty
173 //Create a new instance and clone only config values, not any results maps.
174 XmlReplay replay = new XmlReplay(basedir, this.reportsDir);
175 replay.setControlFileName(controlFile);
176 replay.setProtoHostPort(protoHostPort);
177 replay.setAutoDeletePOSTS(isAutoDeletePOSTS());
178 replay.setDump(dump);
179 replay.setDefaultAuthsMap(getDefaultAuthsMap());
181 //Now run *that* instance.
182 List<ServiceResult> results = replay.runTests(testGroup, test);
184 this.reportsList.addAll(replay.getReportsList()); //Add all the reports from the inner replay, to our master replay's reportsList, to generate the index.html file.
186 XmlReplayReport.saveIndexForMaster(basedir, reportsDir, masterFilename, this.reportsList);
190 /** Use this if you wish to run named tests within a testGroup, otherwise call runTestGroup(). */
191 public List<ServiceResult> runTests(String testGroupID, String testID) throws Exception {
192 List<ServiceResult> result = runXmlReplayFile(this.basedir,
193 this.controlFileName,
196 this.serviceResultsMap,
197 this.autoDeletePOSTS,
200 this.defaultAuthsMap,
206 /** Use this if you wish to specify just ONE test to run within a testGroup, otherwise call runTestGroup(). */
207 public ServiceResult runTest(String testGroupID, String testID) throws Exception {
208 List<ServiceResult> result = runXmlReplayFile(this.basedir,
209 this.controlFileName,
212 this.serviceResultsMap,
213 this.autoDeletePOSTS,
216 this.defaultAuthsMap,
219 if (result.size()>1){
220 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.");
222 return result.get(0);
225 /** Use this if you wish to run all tests within a testGroup.*/
226 public List<ServiceResult> runTestGroup(String testGroupID) throws Exception {
227 //NOTE: calling runTest with empty testID runs all tests in a test group, but don't expose this fact.
228 // Expose this method (runTestGroup) instead.
229 return runTests(testGroupID, "");
232 public List<ServiceResult> autoDelete(String logName){
233 return autoDelete(this.serviceResultsMap, logName, 0);
236 /** Use this method to clean up resources created on the server that returned CSIDs, if you have
237 * specified autoDeletePOSTS==false, which means you are managing the cleanup yourself.
238 * @param serviceResultsMap a Map of ServiceResult objects, which will contain ServiceResult.deleteURL.
239 * @return a List<String> of debug info about which URLs could not be deleted.
241 private static List<ServiceResult> autoDelete(Map<String, ServiceResult> serviceResultsMap, String logName, int reattempt) {
242 List<ServiceResult> results = new ArrayList<ServiceResult>();
243 HashMap<String, ServiceResult> reattemptList = new HashMap<String, ServiceResult>();
244 int deleteFailures = 0;
245 for (ServiceResult pr : serviceResultsMap.values()) {
247 if (pr.autoDelete == true && Tools.notEmpty(pr.deleteURL)){
248 ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, pr.auth, pr.testID, "[autodelete:"+logName+"]");
249 if (deleteResult.gotExpectedResult() == false || deleteResult.responseCode != 200) {
250 reattemptList.put(REATTEMPT_KEY + deleteFailures++, pr); // We need to try again after our dependents have been deleted. cow()
252 results.add(deleteResult);
254 ServiceResult errorResult = new ServiceResult();
255 errorResult.fullURL = pr.fullURL;
256 errorResult.testGroupID = pr.testGroupID;
257 errorResult.fromTestID = pr.fromTestID;
258 errorResult.overrideGotExpectedResult();
259 results.add(errorResult);
261 } catch (Throwable t){
262 String s = (pr!=null) ? "ERROR while cleaning up ServiceResult map: "+pr+" for "+pr.deleteURL+" :: "+t
263 : "ERROR while cleaning up ServiceResult map (null ServiceResult): "+t;
264 System.err.println(s);
265 ServiceResult errorResult = new ServiceResult();
266 errorResult.fullURL = pr.fullURL;
267 errorResult.testGroupID = pr.testGroupID;
268 errorResult.fromTestID = pr.fromTestID;
269 errorResult.error = s;
270 results.add(errorResult);
274 // If there were things we had trouble deleting, it might have been because they had dependents that
275 // needed to be deleted first. Therefore, we're going to try again and again (recursively) up until we reach
276 // our MAX_REATTEMPTS limit.
278 if (reattemptList.size() > 0 && reattempt < MAX_REATTEMPTS) {
279 return autoDelete(reattemptList, logName, ++reattempt); // recursive call
285 public static class AuthsMap {
286 Map<String,String> map;
288 public String getDefaultAuth(){
289 return map.get(defaultID);
291 public String toString(){
292 return "AuthsMap: {default='"+defaultID+"'; "+map.keySet()+'}';
296 public static AuthsMap readAuths(org.dom4j.Document document){
297 Map<String, String> map = new HashMap<String, String>();
298 List<Node> authNodes = document.selectNodes("//auths/auth");
299 for (Node auth : authNodes) {
300 map.put(auth.valueOf("@ID"), auth.getStringValue());
302 AuthsMap authsMap = new AuthsMap();
303 Node auths = document.selectSingleNode("//auths");
304 String defaultID = "";
306 defaultID = auths.valueOf("@default");
309 authsMap.defaultID = defaultID;
313 public static class Dump {
314 public boolean payloads = false;
315 //public static final ServiceResult.DUMP_OPTIONS dumpServiceResultOptions = ServiceResult.DUMP_OPTIONS;
316 public ServiceResult.DUMP_OPTIONS dumpServiceResult = ServiceResult.DUMP_OPTIONS.minimal;
317 public String toString(){
318 return "payloads: "+payloads+" dumpServiceResult: "+dumpServiceResult;
322 public static Dump getDumpConfig(){
326 public static Dump readDumpOptions(org.dom4j.Document document){
327 Dump dump = getDumpConfig();
328 Node dumpNode = document.selectSingleNode("//dump");
329 if (dumpNode != null){
330 dump.payloads = Tools.isTrue(dumpNode.valueOf("@payloads"));
331 String dumpServiceResultStr = dumpNode.valueOf("@dumpServiceResult");
332 if (Tools.notEmpty(dumpServiceResultStr)){
333 dump.dumpServiceResult = ServiceResult.DUMP_OPTIONS.valueOf(dumpServiceResultStr);
339 private static class PartsStruct {
340 public List<Map<String,String>> varsList = new ArrayList<Map<String,String>>();
341 String responseFilename = "";
342 String overrideTestID = "";
343 String startElement = "";
346 public static PartsStruct readParts(Node testNode, final String testID, String xmlReplayBaseDir){
347 PartsStruct resultPartsStruct = new PartsStruct();
348 resultPartsStruct.responseFilename = testNode.valueOf("filename");
349 resultPartsStruct.startElement = testNode.valueOf("startElement");
350 resultPartsStruct.label = testNode.valueOf("label");
351 String responseFilename = testNode.valueOf("filename");
352 if (Tools.notEmpty(responseFilename)){
353 resultPartsStruct.responseFilename = xmlReplayBaseDir + '/' + responseFilename;
354 List<Node> varNodes = testNode.selectNodes("vars/var");
355 readVars(testNode, varNodes, resultPartsStruct);
357 return resultPartsStruct;
360 private static void readVars(Node testNode, List<Node> varNodes, PartsStruct resultPartsStruct){
361 Map<String,String> vars = new HashMap<String,String>();
362 resultPartsStruct.varsList.add(vars);
363 //System.out.println("### vars: "+vars.size()+" ########");
364 for (Node var: varNodes){
365 String ID = var.valueOf("@ID");
366 String value = var.getText();
367 //System.out.println("ID: "+ID+" value: "+value);
368 vars.put(ID, value); //vars is already part of resultPartsStruct.varsList
370 //System.out.println("### end-vars ########");
376 private static String fixupFullURL(String fullURL, String protoHostPort, String uri){
377 if ( ! uri.startsWith(protoHostPort)){
378 fullURL = Tools.glue(protoHostPort, "/", uri);
385 private static String fromTestID(String fullURL, Node testNode, Map<String, ServiceResult> serviceResultsMap){
386 String fromTestID = testNode.valueOf("fromTestID");
387 if (Tools.notEmpty(fromTestID)){
388 ServiceResult getPR = serviceResultsMap.get(fromTestID);
390 fullURL = Tools.glue(fullURL, "/", getPR.location);
396 private static String CSIDfromTestID(Node testNode, Map<String, ServiceResult> serviceResultsMap){
398 String fromTestID = testNode.valueOf("fromTestID");
399 if (Tools.notEmpty(fromTestID)){
400 ServiceResult getPR = serviceResultsMap.get(fromTestID);
402 result = getPR.location;
408 public static org.dom4j.Document getDocument(String xmlFileName) {
409 org.dom4j.Document document = null;
410 SAXReader reader = new SAXReader();
412 document = reader.read(xmlFileName);
413 } catch (DocumentException e) {
414 System.out.println("ERROR reading document: "+e);
415 //e.printStackTrace();
420 protected static String validateResponseSinglePayload(ServiceResult serviceResult,
421 Map<String, ServiceResult> serviceResultsMap,
422 PartsStruct expectedResponseParts,
423 XmlReplayEval evalStruct)
426 byte[] b = FileUtils.readFileToByteArray(new File(expectedResponseParts.responseFilename));
427 String expectedPartContent = new String(b);
428 Map<String,String> vars = expectedResponseParts.varsList.get(0); //just one part, so just one varsList. Must be there, even if empty.
429 expectedPartContent = evalStruct.eval(expectedPartContent, serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc);
430 serviceResult.expectedContentExpanded = expectedPartContent;
431 String label = "NOLABEL";
432 String leftID = "{from expected part, label:"+label+" filename: "+expectedResponseParts.responseFilename+"}";
433 String rightID = "{from server, label:"+label
434 +" fromTestID: "+serviceResult.fromTestID
435 +" URL: "+serviceResult.fullURL
437 String startElement = expectedResponseParts.startElement;
438 String partLabel = expectedResponseParts.label;
439 if (Tools.isBlank(startElement)){
440 if (Tools.notBlank(partLabel))
441 startElement = "/document/*[local-name()='"+partLabel+"']";
443 TreeWalkResults.MatchSpec matchSpec = TreeWalkResults.MatchSpec.createDefault();
444 TreeWalkResults list =
445 XmlCompareJdom.compareParts(expectedPartContent,
447 serviceResult.result,
451 serviceResult.addPartSummary(label, list);
455 protected static String validateResponse(ServiceResult serviceResult,
456 Map<String, ServiceResult> serviceResultsMap,
457 PartsStruct expectedResponseParts,
458 XmlReplayEval evalStruct){
460 if (expectedResponseParts == null) return OK;
461 if (serviceResult == null) return OK;
462 if (serviceResult.result.length() == 0) return OK;
464 return validateResponseSinglePayload(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);
465 } catch (Exception e){
466 String err = "ERROR in XmlReplay.validateResponse() : "+e;
471 //================= runXmlReplayFile ======================================================
473 public static List<ServiceResult> runXmlReplayFile(String xmlReplayBaseDir,
474 String controlFileName,
477 Map<String, ServiceResult> serviceResultsMap,
478 boolean param_autoDeletePOSTS,
480 String protoHostPortParam,
481 AuthsMap defaultAuths,
482 List<String> reportsList,
485 //Internally, we maintain two collections of ServiceResult:
486 // the first is the return value of this method.
487 // the second is the serviceResultsMap, which is used for keeping track of CSIDs created by POSTs, for later reference by DELETE, etc.
488 List<ServiceResult> results = new ArrayList<ServiceResult>();
490 XmlReplayReport report = new XmlReplayReport(reportsDir);
492 String controlFile = Tools.glue(xmlReplayBaseDir, "/", controlFileName);
493 org.dom4j.Document document;
494 document = getDocument(controlFile); //will check full path first, then checks relative to PWD.
496 throw new FileNotFoundException("XmlReplay control file ("+controlFileName+") not found in basedir: "+xmlReplayBaseDir+" Exiting test.");
498 String protoHostPort;
499 if (Tools.isEmpty(protoHostPortParam)){
500 protoHostPort = document.selectSingleNode("/xmlReplay/protoHostPort").getText().trim();
501 System.out.println("DEPRECATED: Using protoHostPort ('"+protoHostPort+"') from xmlReplay file ('"+controlFile+"'), not master.");
503 protoHostPort = protoHostPortParam;
505 if (Tools.isEmpty(protoHostPort)){
506 throw new Exception("XmlReplay control file must have a protoHostPort element");
510 AuthsMap authsMap = readAuths(document);
511 if (authsMap.map.size()==0){
512 authsMap = defaultAuths;
513 authsMapINFO = "Using defaultAuths from master file: "+defaultAuths;
515 authsMapINFO = "Using AuthsMap from control file: "+authsMap;
518 report.addTestGroup(testGroupID, controlFileName); //controlFileName is just the short name, without the full path.
519 String xmlReplayHeader = "========================================================================"
520 +"\r\nXmlReplay running:"
521 +"\r\n controlFile: "+ (new File(controlFile).getCanonicalPath())
522 +"\r\n protoHostPort: "+protoHostPort
523 +"\r\n testGroup: "+testGroupID
524 + (Tools.notEmpty(oneTestID) ? "\r\n oneTestID: "+oneTestID : "")
525 +"\r\n AuthsMap: "+authsMapINFO
526 +"\r\n param_autoDeletePOSTS: "+param_autoDeletePOSTS
527 +"\r\n Dump info: "+dump
528 +"\r\n========================================================================"
530 report.addRunInfo(xmlReplayHeader);
532 System.out.println(xmlReplayHeader);
534 String autoDeletePOSTS = "";
535 List<Node> testgroupNodes;
536 if (Tools.notEmpty(testGroupID)){
537 testgroupNodes = document.selectNodes("//testGroup[@ID='"+testGroupID+"']");
539 testgroupNodes = document.selectNodes("//testGroup");
542 JexlEngine jexl = new JexlEngine(); // Used for expression language expansion from uri field.
543 XmlReplayEval evalStruct = new XmlReplayEval();
544 evalStruct.serviceResultsMap = serviceResultsMap;
545 evalStruct.jexl = jexl;
547 for (Node testgroup : testgroupNodes) {
549 XmlReplayEval.MapContextWKeys jc = new XmlReplayEval.MapContextWKeys();//MapContext(); //Get a new JexlContext for each test group.
552 autoDeletePOSTS = testgroup.valueOf("@autoDeletePOSTS");
554 if (Tools.notEmpty(oneTestID)){
555 tests = testgroup.selectNodes("test[@ID='"+oneTestID+"']");
557 tests = testgroup.selectNodes("test");
559 String authForTest = "";
560 int testElementIndex = -1;
562 for (Node testNode : tests) {
563 long startTime = System.currentTimeMillis();
566 String testID = testNode.valueOf("@ID");
568 // Figure out if we will auto delete resources
569 boolean autoDelete = param_autoDeletePOSTS;
570 String autoDeleteValue = testNode.valueOf("@autoDeletePOSTS");
571 if (autoDeleteValue != null && !autoDeleteValue.trim().isEmpty()) {
572 autoDelete = Boolean.valueOf(autoDeleteValue).booleanValue();
575 String testIDLabel = Tools.notEmpty(testID) ? (testGroupID+'.'+testID) : (testGroupID+'.'+testElementIndex);
576 String method = testNode.valueOf("method");
577 String contentType = testNode.valueOf("contentType");
578 String uri = testNode.valueOf("uri");
579 String fullURL = Tools.glue(protoHostPort, "/", uri);
581 if (contentType == null || contentType.equals("")) {
582 contentType = XmlReplayTransport.APPLICATION_XML;
585 String currentAuthForTest = null;
586 String authIDForTest = testNode.valueOf("@auth");
588 if (Tools.notEmpty(authIDForTest)){
589 currentAuthForTest = authsMap.map.get(authIDForTest);
592 String tokenAuthExpression = testNode.valueOf("@tokenauth");
594 if (Tools.notEmpty(tokenAuthExpression)){
595 currentAuthForTest = "Bearer " + evalStruct.eval(tokenAuthExpression, serviceResultsMap, null, jexl, jc);
599 if (Tools.notEmpty(currentAuthForTest)){
600 authForTest = currentAuthForTest; //else just run with current from last loop;
602 if (Tools.isEmpty(authForTest)){
603 authForTest = defaultAuths.getDefaultAuth();
606 if (uri.indexOf("$")>-1){
607 uri = evalStruct.eval(uri, serviceResultsMap, null, jexl, jc);
609 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
611 List<Integer> expectedCodes = new ArrayList<Integer>();
612 String expectedCodesStr = testNode.valueOf("expectedCodes");
613 if (Tools.notEmpty(expectedCodesStr)){
614 String[] codesArray = expectedCodesStr.split(",");
615 for (String code : codesArray){
616 expectedCodes.add(new Integer(code.trim()));
620 Node responseNode = testNode.selectSingleNode("response");
621 PartsStruct expectedResponseParts = null;
622 if (responseNode!=null){
623 expectedResponseParts = PartsStruct.readParts(responseNode, testID, xmlReplayBaseDir);
624 //System.out.println("reponse parts: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+expectedResponseParts);
627 ServiceResult serviceResult;
628 boolean isPOST = method.equalsIgnoreCase("POST");
629 boolean isPUT = method.equalsIgnoreCase("PUT");
630 if ( isPOST || isPUT ) {
631 PartsStruct parts = PartsStruct.readParts(testNode, testID, xmlReplayBaseDir);
632 if (Tools.notEmpty(parts.overrideTestID)) {
633 testID = parts.overrideTestID;
636 String csid = CSIDfromTestID(testNode, serviceResultsMap);
637 if (Tools.notEmpty(csid)) uri = Tools.glue(uri, "/", csid+"/items/");
639 uri = fromTestID(uri, testNode, serviceResultsMap);
641 //vars only make sense in two contexts: POST/PUT, because you are submitting another file with internal expressions,
642 // and in <response> nodes. For GET, DELETE, there is no payload, so all the URLs with potential expressions are right there in the testNode.
643 Map<String,String> vars = null;
644 if (parts.varsList.size()>0){
645 vars = parts.varsList.get(0);
647 serviceResult = XmlReplayTransport.doPOST_PUTFromXML(parts.responseFilename, vars, protoHostPort, uri, method, contentType, evalStruct, authForTest, testIDLabel);
648 serviceResult.autoDelete = autoDelete;
650 serviceResult.addVars(vars);
652 results.add(serviceResult);
654 serviceResultsMap.put(testID, serviceResult); //PUTs do not return a Location, so don't add PUTs to serviceResultsMap.
656 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
657 } else if (method.equalsIgnoreCase("DELETE")){
658 String fromTestID = testNode.valueOf("fromTestID");
659 ServiceResult pr = Tools.notBlank(fromTestID) ? serviceResultsMap.get(fromTestID) : null;
661 serviceResult = XmlReplayTransport.doDELETE(pr.deleteURL, authForTest, testIDLabel, fromTestID);
662 serviceResult.fromTestID = fromTestID;
663 if (expectedCodes.size()>0){
664 serviceResult.expectedCodes = expectedCodes;
666 results.add(serviceResult);
667 if (serviceResult.codeInSuccessRange(serviceResult.responseCode)){ //gotExpectedResult depends on serviceResult.expectedCodes.
668 serviceResultsMap.remove(fromTestID);
671 if (Tools.notEmpty(fromTestID)){
672 serviceResult = new ServiceResult();
673 serviceResult.responseCode = 0;
674 serviceResult.error = "ID not found in element fromTestID: "+fromTestID;
675 System.err.println("****\r\nServiceResult: "+serviceResult.error+". SKIPPING TEST. Full URL: "+fullURL);
677 serviceResult = XmlReplayTransport.doDELETE(fullURL, authForTest, testID, fromTestID);
679 serviceResult.fromTestID = fromTestID;
680 results.add(serviceResult);
682 } else if (method.equalsIgnoreCase("GET")){
683 fullURL = fromTestID(fullURL, testNode, serviceResultsMap);
684 serviceResult = XmlReplayTransport.doGET(fullURL, authForTest, testIDLabel);
685 results.add(serviceResult);
686 serviceResultsMap.put(testID, serviceResult);
687 } else if (method.equalsIgnoreCase("LIST")){
688 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
689 String listQueryParams = ""; //TODO: empty for now, later may pick up from XML control file.
690 serviceResult = XmlReplayTransport.doLIST(fullURL, listQueryParams, authForTest, testIDLabel);
691 results.add(serviceResult);
692 serviceResultsMap.put(testID, serviceResult);
694 throw new Exception("HTTP method not supported by XmlReplay: "+method);
697 serviceResult.testID = testID;
698 serviceResult.fullURL = fullURL;
699 serviceResult.auth = authForTest;
700 serviceResult.method = method;
701 if (expectedCodes.size()>0){
702 serviceResult.expectedCodes = expectedCodes;
704 if (Tools.isEmpty(serviceResult.testID)) serviceResult.testID = testIDLabel;
705 if (Tools.isEmpty(serviceResult.testGroupID)) serviceResult.testGroupID = testGroupID;
707 Node expectedLevel = testNode.selectSingleNode("response/expected");
708 if (expectedLevel!=null){
709 String level = expectedLevel.valueOf("@level");
710 serviceResult.payloadStrictness = level;
712 //=====================================================
713 // ALL VALIDATION FOR ALL REQUESTS IS DONE HERE:
714 //=====================================================
715 boolean hasError = false;
716 String vError = validateResponse(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);
717 if (Tools.notEmpty(vError)){
718 serviceResult.error = vError;
719 serviceResult.failureReason = " : VALIDATION ERROR; ";
722 if (hasError == false){
723 hasError = ! serviceResult.gotExpectedResult();
726 boolean doingAuto = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.auto);
727 String serviceResultRow = serviceResult.dump(dump.dumpServiceResult, hasError)+"; time:"+(System.currentTimeMillis()-startTime);
728 String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:"+testIDLabel+": ": "";
730 report.addTestResult(serviceResult);
732 if ( (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed)
733 || (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.full) ){
734 System.out.println("\r\n#---------------------#");
736 System.out.println(timeString()+" "+leader+serviceResultRow+"\r\n");
737 if (dump.payloads || (doingAuto&&hasError) ) {
738 if (Tools.notBlank(serviceResult.requestPayload)){
739 System.out.println("\r\n========== request payload ===============");
740 System.out.println(serviceResult.requestPayload);
741 System.out.println("==========================================\r\n");
744 if (dump.payloads || (doingAuto&&hasError)) {
745 if (Tools.notBlank(serviceResult.result)){
746 System.out.println("\r\n========== response payload ==============");
747 System.out.println(serviceResult.result);
748 System.out.println("==========================================\r\n");
751 } catch (Throwable t) {
752 String msg = "ERROR: XmlReplay experienced an error in a test node: "+testNode+" Throwable: "+t;
753 System.out.println(msg);
754 System.out.println(Tools.getStackTrace(t));
755 ServiceResult serviceResult = new ServiceResult();
756 serviceResult.error = msg;
757 serviceResult.failureReason = " : SYSTEM ERROR; ";
758 results.add(serviceResult);
761 if (Tools.isTrue(autoDeletePOSTS) && param_autoDeletePOSTS){
762 autoDelete(serviceResultsMap, "default", 0);
766 //=== Now spit out the HTML report file ===
767 File m = new File(controlFileName);
768 String localName = m.getName();//don't instantiate, just use File to extract file name without directory.
769 String reportName = localName+'-'+testGroupID+".html";
771 File resultFile = report.saveReport(xmlReplayBaseDir, reportsDir, reportName);
772 if (resultFile!=null) {
773 String toc = report.getTOC(reportName);
774 reportsList.add(toc);
776 //================================
781 private static String timeString() {
782 java.util.Date date= new java.util.Date();
783 java.sql.Timestamp ts = new java.sql.Timestamp(date.getTime());
784 return ts.toString();
788 //======================== MAIN ===================================================================
790 private static Options createOptions() {
791 Options options = new Options();
792 options.addOption("xmlReplayBaseDir", true, "default/basedir");
796 public static String usage(){
797 String result = "org.collectionspace.services.IntegrationTests.xmlreplay.XmlReplay {args}\r\n"
798 +" -xmlReplayBaseDir <dir> \r\n"
799 +" You may also override these with system args, e.g.: \r\n"
800 +" -DxmlReplayBaseDir=/path/to/dir \r\n"
801 +" These may also be passed in via the POM.\r\n"
802 +" You can also set these system args, e.g.: \r\n"
803 +" -DtestGroupID=<oneID> \r\n"
804 +" -DtestID=<one TestGroup ID>"
805 +" -DautoDeletePOSTS=<true|false> \r\n"
806 +" (note: -DautoDeletePOSTS won't force deletion if set to false in control file.";
810 private static String opt(CommandLine line, String option){
812 String fromProps = System.getProperty(option);
813 if (Tools.notEmpty(fromProps)){
819 result = line.getOptionValue(option);
826 public static void main(String[]args) throws Exception {
827 Options options = createOptions();
828 //System.out.println("System CLASSPATH: "+prop.getProperty("java.class.path", null));
829 CommandLineParser parser = new GnuParser();
831 // parse the command line arguments
832 CommandLine line = parser.parse(options, args);
834 String xmlReplayBaseDir = opt(line, "xmlReplayBaseDir");
835 String reportsDir = opt(line, "reportsDir");
836 String testGroupID = opt(line, "testGroupID");
837 String testID = opt(line, "testID");
838 String autoDeletePOSTS = opt(line, "autoDeletePOSTS");
839 String dumpResults = opt(line, "dumpResults");
840 String controlFilename = opt(line, "controlFilename");
841 String xmlReplayMaster = opt(line, "xmlReplayMaster");
843 if (Tools.isBlank(reportsDir)){
844 reportsDir = xmlReplayBaseDir + XmlReplayTest.REPORTS_DIRNAME;
846 reportsDir = Tools.fixFilename(reportsDir);
847 xmlReplayBaseDir = Tools.fixFilename(xmlReplayBaseDir);
848 controlFilename = Tools.fixFilename(controlFilename);
850 boolean bAutoDeletePOSTS = true;
851 if (Tools.notEmpty(autoDeletePOSTS)) {
852 bAutoDeletePOSTS = Tools.isTrue(autoDeletePOSTS);
854 boolean bDumpResults = false;
855 if (Tools.notEmpty(dumpResults)) {
856 bDumpResults = Tools.isTrue(autoDeletePOSTS);
858 if (Tools.isEmpty(xmlReplayBaseDir)){
859 System.err.println("ERROR: xmlReplayBaseDir was not specified.");
862 File f = new File(Tools.glue(xmlReplayBaseDir, "/", controlFilename));
863 if (Tools.isEmpty(xmlReplayMaster) && !f.exists()){
864 System.err.println("Control file not found: "+f.getCanonicalPath());
867 File fMaster = new File(Tools.glue(xmlReplayBaseDir, "/", xmlReplayMaster));
868 if (Tools.notEmpty(xmlReplayMaster) && !fMaster.exists()){
869 System.err.println("Master file not found: "+fMaster.getCanonicalPath());
873 String xmlReplayBaseDirResolved = (new File(xmlReplayBaseDir)).getCanonicalPath();
874 System.out.println("XmlReplay ::"
875 + "\r\n xmlReplayBaseDir: "+xmlReplayBaseDir
876 + "\r\n xmlReplayBaseDir(resolved): "+xmlReplayBaseDirResolved
877 + "\r\n controlFilename: "+controlFilename
878 + "\r\n xmlReplayMaster: "+xmlReplayMaster
879 + "\r\n testGroupID: "+testGroupID
880 + "\r\n testID: "+testID
881 + "\r\n autoDeletePOSTS: "+bAutoDeletePOSTS
882 + (Tools.notEmpty(xmlReplayMaster)
883 ? ("\r\n will use master file: "+fMaster.getCanonicalPath())
884 : ("\r\n will use control file: "+f.getCanonicalPath()) )
887 if (Tools.notEmpty(xmlReplayMaster)){
888 if (Tools.notEmpty(controlFilename)){
889 System.out.println("WARN: controlFilename: "+controlFilename+" will not be used because master was specified. Running master: "+xmlReplayMaster);
891 XmlReplay replay = new XmlReplay(xmlReplayBaseDirResolved, reportsDir);
892 replay.readOptionsFromMasterConfigFile(xmlReplayMaster);
893 replay.setAutoDeletePOSTS(bAutoDeletePOSTS);
894 Dump dumpFromMaster = replay.getDump();
895 dumpFromMaster.payloads = Tools.isTrue(dumpResults);
896 replay.setDump(dumpFromMaster);
897 replay.runMaster(xmlReplayMaster, false); //false, because we already just read the options, and override a few.
899 Dump dump = getDumpConfig();
900 dump.payloads = Tools.isTrue(dumpResults);
901 List<String> reportsList = new ArrayList<String>();
902 runXmlReplayFile(xmlReplayBaseDirResolved, controlFilename, testGroupID, testID, createResultsMap(), bAutoDeletePOSTS, dump, "", null, reportsList, reportsDir);
903 System.out.println("DEPRECATED: reportsList is generated, but not dumped: "+reportsList.toString());
905 } catch (ParseException exp) {
906 // oops, something went wrong
907 System.err.println("Cmd-line parsing failed. Reason: " + exp.getMessage());
908 System.err.println(usage());
909 } catch (Exception e) {
910 System.out.println("Error : " + e.getMessage());