1 package org.collectionspace.services.IntegrationTests.xmlreplay;
\r
3 import org.apache.commons.cli.*;
\r
5 import org.apache.commons.io.FileUtils;
\r
6 import org.apache.commons.jexl2.JexlEngine;
\r
7 import org.collectionspace.services.common.api.FileTools;
\r
8 import org.collectionspace.services.common.api.Tools;
\r
9 import org.collectionspace.services.common.service.ServiceBindingType;
\r
11 import org.dom4j.io.SAXReader;
\r
16 /** This class is used to replay a request to the Services layer, by sending the XML payload
\r
17 * in an appropriate Multipart request.
\r
18 * See example usage in calling class XmlReplayTest in services/IntegrationTests, and also in main() in this class.
\r
19 * @author Laramie Crocker
\r
21 public class XmlReplay {
\r
23 public XmlReplay(String basedir){
\r
24 this.basedir = basedir;
\r
25 this.serviceResultsMap = createResultsMap();
\r
26 this.reportsList = new ArrayList<String>();
\r
29 public static final String DEFAULT_CONTROL = "xml-replay-control.xml";
\r
30 public static final String DEFAULT_MASTER_CONTROL = "xml-replay-master.xml";
\r
31 public static final String DEFAULT_DEV_MASTER_CONTROL = "dev-master.xml";
\r
33 private String basedir = "."; //set from constructor.
\r
34 public String getBaseDir(){
\r
38 private String controlFileName = DEFAULT_CONTROL;
\r
39 public String getControlFileName() {
\r
40 return controlFileName;
\r
42 public void setControlFileName(String controlFileName) {
\r
43 this.controlFileName = controlFileName;
\r
46 private String protoHostPort = "";
\r
47 public String getProtoHostPort() {
\r
48 return protoHostPort;
\r
50 public void setProtoHostPort(String protoHostPort) {
\r
51 this.protoHostPort = protoHostPort;
\r
54 private boolean autoDeletePOSTS = true;
\r
55 public boolean isAutoDeletePOSTS() {
\r
56 return autoDeletePOSTS;
\r
58 public void setAutoDeletePOSTS(boolean autoDeletePOSTS) {
\r
59 this.autoDeletePOSTS = autoDeletePOSTS;
\r
63 public Dump getDump() {
\r
66 public void setDump(Dump dump) {
\r
70 AuthsMap defaultAuthsMap;
\r
71 public AuthsMap getDefaultAuthsMap(){
\r
72 return defaultAuthsMap;
\r
74 public void setDefaultAuthsMap(AuthsMap authsMap){
\r
75 defaultAuthsMap = authsMap;
\r
78 private Map<String, ServiceResult> serviceResultsMap;
\r
79 public Map<String, ServiceResult> getServiceResultsMap(){
\r
80 return serviceResultsMap;
\r
82 public static Map<String, ServiceResult> createResultsMap(){
\r
83 return new HashMap<String, ServiceResult>();
\r
86 private List<String> reportsList;
\r
87 public List<String> getReportsList(){
\r
91 public String toString(){
\r
92 return "XmlReplay{"+this.basedir+", "+this.defaultAuthsMap+", "+this.dump+'}';
\r
95 // ============== METHODS ===========================================================
\r
97 /** Optional information method: call this method after instantiating this class using the constructor XmlReplay(String), which sets the basedir. Then you
\r
98 * pass in your relative masterFilename to that basedir to this method, which will return true if the file is readable, valid xml, etc.
\r
99 * 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.
\r
100 * 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.
\r
102 public boolean masterConfigFileExists(String masterFilename){
\r
104 org.dom4j.Document doc = openMasterConfigFile(masterFilename);
\r
109 } catch (Throwable t){
\r
114 public org.dom4j.Document openMasterConfigFile(String masterFilename) throws FileNotFoundException {
\r
115 String fullPath = Tools.glue(basedir, "/", masterFilename);
\r
116 File f = new File(fullPath);
\r
120 org.dom4j.Document document = getDocument(fullPath); //will check full path first, then checks relative to PWD.
\r
121 if (document == null){
\r
122 throw new FileNotFoundException("XmlReplay master control file ("+masterFilename+") not found in basedir: "+basedir+". Exiting test.");
\r
127 /** specify the master config file, relative to getBaseDir(), but ignore any tests or testGroups in the master.
\r
128 * @return a Document object, which you don't need to use: all options will be stored in XmlReplay instance.
\r
130 public org.dom4j.Document readOptionsFromMasterConfigFile(String masterFilename) throws FileNotFoundException {
\r
131 org.dom4j.Document document = openMasterConfigFile(masterFilename);
\r
132 if (document == null){
\r
133 throw new FileNotFoundException(masterFilename);
\r
135 protoHostPort = document.selectSingleNode("/xmlReplayMaster/protoHostPort").getText().trim();
\r
136 AuthsMap authsMap = readAuths(document);
\r
137 setDefaultAuthsMap(authsMap);
\r
138 Dump dump = XmlReplay.readDumpOptions(document);
\r
143 public List<List<ServiceResult>> runMaster(String masterFilename) throws Exception {
\r
144 return runMaster(masterFilename, true);
\r
147 /** Creates new instances of XmlReplay, one for each controlFile specified in the master,
\r
148 * and setting defaults from this instance, but not sharing ServiceResult objects or maps. */
\r
149 public List<List<ServiceResult>> runMaster(String masterFilename, boolean readOptionsFromMaster) throws Exception {
\r
150 List<List<ServiceResult>> list = new ArrayList<List<ServiceResult>>();
\r
151 org.dom4j.Document document;
\r
152 if (readOptionsFromMaster){
\r
153 document = readOptionsFromMasterConfigFile(masterFilename);
\r
155 document = openMasterConfigFile(masterFilename);
\r
157 if (document==null){
\r
158 throw new FileNotFoundException(masterFilename);
\r
160 String controlFile, testGroup, test;
\r
161 List<Node> runNodes;
\r
162 runNodes = document.selectNodes("/xmlReplayMaster/run");
\r
163 for (Node runNode : runNodes) {
\r
164 controlFile = runNode.valueOf("@controlFile");
\r
165 testGroup = runNode.valueOf("@testGroup");
\r
166 test = runNode.valueOf("@test"); //may be empty
\r
168 //Create a new instance and clone only config values, not any results maps.
\r
169 XmlReplay replay = new XmlReplay(basedir);
\r
170 replay.setControlFileName(controlFile);
\r
171 replay.setProtoHostPort(protoHostPort);
\r
172 replay.setAutoDeletePOSTS(isAutoDeletePOSTS());
\r
173 replay.setDump(dump);
\r
174 replay.setDefaultAuthsMap(getDefaultAuthsMap());
\r
176 //Now run *that* instance.
\r
177 List<ServiceResult> results = replay.runTests(testGroup, test);
\r
179 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.
\r
181 StringBuffer sb = new StringBuffer(XmlReplayReport.HTML_PAGE_START);
\r
182 String dateStr = Tools.nowLocale();
\r
183 sb.append("<div class='REPORTTIME'>XmlReplay run "+dateStr+" master: "+masterFilename+"</div>");
\r
184 for (String oneToc: this.reportsList){
\r
185 sb.append(oneToc).append("<hr />");
\r
187 sb.append(XmlReplayReport.HTML_PAGE_END);
\r
188 FileTools.saveFile(getReportsDir(this.basedir),"index."+masterFilename+".html", sb.toString(), false);
\r
192 /** Use this if you wish to run named tests within a testGroup, otherwise call runTestGroup(). */
\r
193 public List<ServiceResult> runTests(String testGroupID, String testID) throws Exception {
\r
194 List<ServiceResult> result = runXmlReplayFile(this.basedir,
\r
195 this.controlFileName,
\r
198 this.serviceResultsMap,
\r
199 this.autoDeletePOSTS,
\r
201 this.protoHostPort,
\r
202 this.defaultAuthsMap,
\r
207 /** Use this if you wish to specify just ONE test to run within a testGroup, otherwise call runTestGroup(). */
\r
208 public ServiceResult runTest(String testGroupID, String testID) throws Exception {
\r
209 List<ServiceResult> result = runXmlReplayFile(this.basedir,
\r
210 this.controlFileName,
\r
213 this.serviceResultsMap,
\r
214 this.autoDeletePOSTS,
\r
216 this.protoHostPort,
\r
217 this.defaultAuthsMap,
\r
219 if (result.size()>1){
\r
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.");
\r
222 return result.get(0);
\r
225 /** Use this if you wish to run all tests within a testGroup.*/
\r
226 public List<ServiceResult> runTestGroup(String testGroupID) throws Exception {
\r
227 //NOTE: calling runTest with empty testID runs all tests in a test group, but don't expose this fact.
\r
228 // Expose this method (runTestGroup) instead.
\r
229 return runTests(testGroupID, "");
\r
232 public List<ServiceResult> autoDelete(String logName){
\r
233 return autoDelete(this.serviceResultsMap, logName);
\r
236 /** Use this method to clean up resources created on the server that returned CSIDs, if you have
\r
237 * specified autoDeletePOSTS==false, which means you are managing the cleanup yourself.
\r
238 * @param serviceResultsMap a Map of ServiceResult objects, which will contain ServiceResult.deleteURL.
\r
239 * @return a List<String> of debug info about which URLs could not be deleted.
\r
241 public static List<ServiceResult> autoDelete(Map<String, ServiceResult> serviceResultsMap, String logName){
\r
242 List<ServiceResult> results = new ArrayList<ServiceResult>();
\r
243 for (ServiceResult pr : serviceResultsMap.values()){
\r
245 if (Tools.notEmpty(pr.deleteURL)){
\r
246 ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, pr.auth, pr.testID, "[autodelete:"+logName+"]");
\r
247 results.add(deleteResult);
\r
249 ServiceResult errorResult = new ServiceResult();
\r
250 errorResult.fullURL = pr.fullURL;
\r
251 errorResult.testGroupID = pr.testGroupID;
\r
252 errorResult.fromTestID = pr.fromTestID;
\r
253 errorResult.overrideGotExpectedResult();
\r
254 results.add(errorResult);
\r
256 } catch (Throwable t){
\r
257 String s = (pr!=null) ? "ERROR while cleaning up ServiceResult map: "+pr+" for "+pr.deleteURL+" :: "+t
\r
258 : "ERROR while cleaning up ServiceResult map (null ServiceResult): "+t;
\r
259 System.err.println(s);
\r
260 ServiceResult errorResult = new ServiceResult();
\r
261 errorResult.fullURL = pr.fullURL;
\r
262 errorResult.testGroupID = pr.testGroupID;
\r
263 errorResult.fromTestID = pr.fromTestID;
\r
264 errorResult.error = s;
\r
265 results.add(errorResult);
\r
271 public static class AuthsMap {
\r
272 Map<String,String> map;
\r
273 String defaultID="";
\r
274 public String getDefaultAuth(){
\r
275 return map.get(defaultID);
\r
277 public String toString(){
\r
278 return "AuthsMap: {default='"+defaultID+"'; "+map.keySet()+'}';
\r
282 public static AuthsMap readAuths(org.dom4j.Document document){
\r
283 Map<String, String> map = new HashMap<String, String>();
\r
284 List<Node> authNodes = document.selectNodes("//auths/auth");
\r
285 for (Node auth : authNodes) {
\r
286 map.put(auth.valueOf("@ID"), auth.getStringValue());
\r
288 AuthsMap authsMap = new AuthsMap();
\r
289 Node auths = document.selectSingleNode("//auths");
\r
290 String defaultID = "";
\r
291 if (auths != null){
\r
292 defaultID = auths.valueOf("@default");
\r
294 authsMap.map = map;
\r
295 authsMap.defaultID = defaultID;
\r
299 public static class Dump {
\r
300 public boolean payloads = false;
\r
301 //public static final ServiceResult.DUMP_OPTIONS dumpServiceResultOptions = ServiceResult.DUMP_OPTIONS;
\r
302 public ServiceResult.DUMP_OPTIONS dumpServiceResult = ServiceResult.DUMP_OPTIONS.minimal;
\r
303 public String toString(){
\r
304 return "payloads: "+payloads+" dumpServiceResult: "+dumpServiceResult;
\r
308 public static Dump getDumpConfig(){
\r
312 public static Dump readDumpOptions(org.dom4j.Document document){
\r
313 Dump dump = getDumpConfig();
\r
314 Node dumpNode = document.selectSingleNode("//dump");
\r
315 if (dumpNode != null){
\r
316 dump.payloads = Tools.isTrue(dumpNode.valueOf("@payloads"));
\r
317 String dumpServiceResultStr = dumpNode.valueOf("@dumpServiceResult");
\r
318 if (Tools.notEmpty(dumpServiceResultStr)){
\r
319 dump.dumpServiceResult = ServiceResult.DUMP_OPTIONS.valueOf(dumpServiceResultStr);
\r
325 private static class PartsStruct {
\r
326 public List<Map<String,String>> varsList = new ArrayList<Map<String,String>>();
\r
327 String responseFilename = "";
\r
328 String overrideTestID = "";
\r
329 String startElement = "";
\r
332 public static PartsStruct readParts(Node testNode, final String testID, String xmlReplayBaseDir){
\r
333 PartsStruct resultPartsStruct = new PartsStruct();
\r
334 resultPartsStruct.responseFilename = testNode.valueOf("filename");
\r
335 resultPartsStruct.startElement = testNode.valueOf("startElement");
\r
336 resultPartsStruct.label = testNode.valueOf("label");
\r
337 String responseFilename = testNode.valueOf("filename");
\r
338 if (Tools.notEmpty(responseFilename)){
\r
339 resultPartsStruct.responseFilename = xmlReplayBaseDir + '/' + responseFilename;
\r
340 List<Node> varNodes = testNode.selectNodes("vars/var");
\r
341 readVars(testNode, varNodes, resultPartsStruct);
\r
343 return resultPartsStruct;
\r
346 private static void readVars(Node testNode, List<Node> varNodes, PartsStruct resultPartsStruct){
\r
347 Map<String,String> vars = new HashMap<String,String>();
\r
348 resultPartsStruct.varsList.add(vars);
\r
349 //System.out.println("### vars: "+vars.size()+" ########");
\r
350 for (Node var: varNodes){
\r
351 String ID = var.valueOf("@ID");
\r
352 String value = var.getText();
\r
353 //System.out.println("ID: "+ID+" value: "+value);
\r
354 vars.put(ID, value); //vars is already part of resultPartsStruct.varsList
\r
356 //System.out.println("### end-vars ########");
\r
362 private static String fixupFullURL(String fullURL, String protoHostPort, String uri){
\r
363 if ( ! uri.startsWith(protoHostPort)){
\r
364 fullURL = Tools.glue(protoHostPort, "/", uri);
\r
371 private static String fromTestID(String fullURL, Node testNode, Map<String, ServiceResult> serviceResultsMap){
\r
372 String fromTestID = testNode.valueOf("fromTestID");
\r
373 if (Tools.notEmpty(fromTestID)){
\r
374 ServiceResult getPR = serviceResultsMap.get(fromTestID);
\r
375 if (getPR != null){
\r
376 fullURL = Tools.glue(fullURL, "/", getPR.location);
\r
382 private static String CSIDfromTestID(Node testNode, Map<String, ServiceResult> serviceResultsMap){
\r
383 String result = "";
\r
384 String fromTestID = testNode.valueOf("fromTestID");
\r
385 if (Tools.notEmpty(fromTestID)){
\r
386 ServiceResult getPR = serviceResultsMap.get(fromTestID);
\r
387 if (getPR != null){
\r
388 result = getPR.location;
\r
394 public static org.dom4j.Document getDocument(String xmlFileName) {
\r
395 org.dom4j.Document document = null;
\r
396 SAXReader reader = new SAXReader();
\r
398 document = reader.read(xmlFileName);
\r
399 } catch (DocumentException e) {
\r
400 System.out.println("ERROR reading document: "+e);
\r
401 //e.printStackTrace();
\r
406 protected static String validateResponseSinglePayload(ServiceResult serviceResult,
\r
407 Map<String, ServiceResult> serviceResultsMap,
\r
408 PartsStruct expectedResponseParts,
\r
409 XmlReplayEval evalStruct)
\r
412 byte[] b = FileUtils.readFileToByteArray(new File(expectedResponseParts.responseFilename));
\r
413 String expectedPartContent = new String(b);
\r
414 Map<String,String> vars = expectedResponseParts.varsList.get(0); //just one part, so just one varsList. Must be there, even if empty.
\r
415 expectedPartContent = evalStruct.eval(expectedPartContent, serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc);
\r
416 String label = "NOLABEL";
\r
417 String leftID = "{from expected part, label:"+label+" filename: "+expectedResponseParts.responseFilename+"}";
\r
418 String rightID = "{from server, label:"+label
\r
419 +" fromTestID: "+serviceResult.fromTestID
\r
420 +" URL: "+serviceResult.fullURL
\r
422 String startElement = expectedResponseParts.startElement;
\r
423 String partLabel = expectedResponseParts.label;
\r
424 if (Tools.isBlank(startElement)){
\r
425 if (Tools.notBlank(partLabel))
\r
426 startElement = "/document/*[local-name()='"+partLabel+"']";
\r
428 TreeWalkResults.MatchSpec matchSpec = TreeWalkResults.MatchSpec.createDefault();
\r
429 TreeWalkResults list =
\r
430 XmlCompareJdom.compareParts(expectedPartContent,
\r
432 serviceResult.result,
\r
436 serviceResult.addPartSummary(label, list);
\r
440 protected static String validateResponse(ServiceResult serviceResult,
\r
441 Map<String, ServiceResult> serviceResultsMap,
\r
442 PartsStruct expectedResponseParts,
\r
443 XmlReplayEval evalStruct){
\r
445 if (expectedResponseParts == null) return OK;
\r
446 if (serviceResult == null) return OK;
\r
447 if (serviceResult.result.length() == 0) return OK;
\r
449 return validateResponseSinglePayload(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);
\r
450 } catch (Exception e){
\r
451 String err = "ERROR in XmlReplay.validateResponse() : "+e;
\r
456 //================= runXmlReplayFile ======================================================
\r
458 public static List<ServiceResult> runXmlReplayFile(String xmlReplayBaseDir,
\r
459 String controlFileName,
\r
460 String testGroupID,
\r
462 Map<String, ServiceResult> serviceResultsMap,
\r
463 boolean param_autoDeletePOSTS,
\r
465 String protoHostPortParam,
\r
466 AuthsMap defaultAuths,
\r
467 List<String> reportsList)
\r
469 //Internally, we maintain two collections of ServiceResult:
\r
470 // the first is the return value of this method.
\r
471 // the second is the serviceResultsMap, which is used for keeping track of CSIDs created by POSTs, for later reference by DELETE, etc.
\r
472 List<ServiceResult> results = new ArrayList<ServiceResult>();
\r
474 XmlReplayReport report = new XmlReplayReport();
\r
476 String controlFile = Tools.glue(xmlReplayBaseDir, "/", controlFileName);
\r
477 org.dom4j.Document document;
\r
478 document = getDocument(controlFile); //will check full path first, then checks relative to PWD.
\r
479 if (document==null){
\r
480 throw new FileNotFoundException("XmlReplay control file ("+controlFileName+") not found in basedir: "+xmlReplayBaseDir+" Exiting test.");
\r
482 String protoHostPort;
\r
483 if (Tools.isEmpty(protoHostPortParam)){
\r
484 protoHostPort = document.selectSingleNode("/xmlReplay/protoHostPort").getText().trim();
\r
485 System.out.println("DEPRECATED: Using protoHostPort ('"+protoHostPort+"') from xmlReplay file ('"+controlFile+"'), not master.");
\r
487 protoHostPort = protoHostPortParam;
\r
489 if (Tools.isEmpty(protoHostPort)){
\r
490 throw new Exception("XmlReplay control file must have a protoHostPort element");
\r
493 String authsMapINFO;
\r
494 AuthsMap authsMap = readAuths(document);
\r
495 if (authsMap.map.size()==0){
\r
496 authsMap = defaultAuths;
\r
497 authsMapINFO = "Using defaultAuths from master file: "+defaultAuths;
\r
499 authsMapINFO = "Using AuthsMap from control file: "+authsMap;
\r
502 report.addTestGroup(testGroupID, controlFileName); //controlFileName is just the short name, without the full path.
\r
503 String xmlReplayHeader = "========================================================================"
\r
504 +"\r\nXmlReplay running:"
\r
505 +"\r\n controlFile: "+ (new File(controlFile).getCanonicalPath())
\r
506 +"\r\n protoHostPort: "+protoHostPort
\r
507 +"\r\n testGroup: "+testGroupID
\r
508 + (Tools.notEmpty(oneTestID) ? "\r\n oneTestID: "+oneTestID : "")
\r
509 +"\r\n AuthsMap: "+authsMapINFO
\r
510 +"\r\n param_autoDeletePOSTS: "+param_autoDeletePOSTS
\r
511 +"\r\n Dump info: "+dump
\r
512 +"\r\n========================================================================"
\r
514 report.addRunInfo(xmlReplayHeader);
\r
516 System.out.println(xmlReplayHeader);
\r
518 String autoDeletePOSTS = "";
\r
519 List<Node> testgroupNodes;
\r
520 if (Tools.notEmpty(testGroupID)){
\r
521 testgroupNodes = document.selectNodes("//testGroup[@ID='"+testGroupID+"']");
\r
523 testgroupNodes = document.selectNodes("//testGroup");
\r
526 JexlEngine jexl = new JexlEngine(); // Used for expression language expansion from uri field.
\r
527 XmlReplayEval evalStruct = new XmlReplayEval();
\r
528 evalStruct.serviceResultsMap = serviceResultsMap;
\r
529 evalStruct.jexl = jexl;
\r
531 for (Node testgroup : testgroupNodes) {
\r
533 XmlReplayEval.MapContextWKeys jc = new XmlReplayEval.MapContextWKeys();//MapContext(); //Get a new JexlContext for each test group.
\r
534 evalStruct.jc = jc;
\r
536 autoDeletePOSTS = testgroup.valueOf("@autoDeletePOSTS");
\r
538 if (Tools.notEmpty(oneTestID)){
\r
539 tests = testgroup.selectNodes("test[@ID='"+oneTestID+"']");
\r
541 tests = testgroup.selectNodes("test");
\r
543 String authForTest = "";
\r
544 int testElementIndex = -1;
\r
546 for (Node testNode : tests) {
\r
547 long startTime = System.currentTimeMillis();
\r
549 testElementIndex++;
\r
550 String testID = testNode.valueOf("@ID");
\r
551 String testIDLabel = Tools.notEmpty(testID) ? (testGroupID+'.'+testID) : (testGroupID+'.'+testElementIndex);
\r
552 String method = testNode.valueOf("method");
\r
553 String uri = testNode.valueOf("uri");
\r
554 String fullURL = Tools.glue(protoHostPort, "/", uri);
\r
556 String authIDForTest = testNode.valueOf("@auth");
\r
557 String currentAuthForTest = authsMap.map.get(authIDForTest);
\r
558 if (Tools.notEmpty(currentAuthForTest)){
\r
559 authForTest = currentAuthForTest; //else just run with current from last loop;
\r
561 if (Tools.isEmpty(authForTest)){
\r
562 authForTest = defaultAuths.getDefaultAuth();
\r
565 if (uri.indexOf("$")>-1){
\r
566 uri = evalStruct.eval(uri, serviceResultsMap, null, jexl, jc);
\r
568 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
\r
570 List<Integer> expectedCodes = new ArrayList<Integer>();
\r
571 String expectedCodesStr = testNode.valueOf("expectedCodes");
\r
572 if (Tools.notEmpty(expectedCodesStr)){
\r
573 String[] codesArray = expectedCodesStr.split(",");
\r
574 for (String code : codesArray){
\r
575 expectedCodes.add(new Integer(code.trim()));
\r
579 Node responseNode = testNode.selectSingleNode("response");
\r
580 PartsStruct expectedResponseParts = null;
\r
581 if (responseNode!=null){
\r
582 expectedResponseParts = PartsStruct.readParts(responseNode, testID, xmlReplayBaseDir);
\r
583 //System.out.println("reponse parts: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+expectedResponseParts);
\r
586 ServiceResult serviceResult;
\r
587 boolean isPOST = method.equalsIgnoreCase("POST");
\r
588 boolean isPUT = method.equalsIgnoreCase("PUT");
\r
589 if ( isPOST || isPUT ) {
\r
590 PartsStruct parts = PartsStruct.readParts(testNode, testID, xmlReplayBaseDir);
\r
591 if (Tools.notEmpty(parts.overrideTestID)) {
\r
592 testID = parts.overrideTestID;
\r
595 String csid = CSIDfromTestID(testNode, serviceResultsMap);
\r
596 if (Tools.notEmpty(csid)) uri = Tools.glue(uri, "/", csid+"/items/");
\r
597 } else if (isPUT) {
\r
598 uri = fromTestID(uri, testNode, serviceResultsMap);
\r
600 //vars only make sense in two contexts: POST/PUT, because you are submitting another file with internal expressions,
\r
601 // and in <response> nodes. For GET, DELETE, there is no payload, so all the URLs with potential expressions are right there in the testNode.
\r
602 Map<String,String> vars = null;
\r
603 if (parts.varsList.size()>0){
\r
604 vars = parts.varsList.get(0);
\r
606 serviceResult = XmlReplayTransport.doPOST_PUTFromXML(parts.responseFilename, vars, protoHostPort, uri, method, XmlReplayTransport.APPLICATION_XML, evalStruct, authForTest, testIDLabel);
\r
608 serviceResult.addVars(vars);
\r
610 results.add(serviceResult);
\r
612 serviceResultsMap.put(testID, serviceResult); //PUTs do not return a Location, so don't add PUTs to serviceResultsMap.
\r
614 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
\r
615 } else if (method.equalsIgnoreCase("DELETE")){
\r
616 String fromTestID = testNode.valueOf("fromTestID");
\r
617 ServiceResult pr = serviceResultsMap.get(fromTestID);
\r
619 serviceResult = XmlReplayTransport.doDELETE(pr.deleteURL, authForTest, testIDLabel, fromTestID);
\r
620 serviceResult.fromTestID = fromTestID;
\r
621 if (expectedCodes.size()>0){
\r
622 serviceResult.expectedCodes = expectedCodes;
\r
624 results.add(serviceResult);
\r
625 if (serviceResult.codeInSuccessRange(serviceResult.responseCode)){ //gotExpectedResult depends on serviceResult.expectedCodes.
\r
626 serviceResultsMap.remove(fromTestID);
\r
629 if (Tools.notEmpty(fromTestID)){
\r
630 serviceResult = new ServiceResult();
\r
631 serviceResult.responseCode = 0;
\r
632 serviceResult.error = "ID not found in element fromTestID: "+fromTestID;
\r
633 System.err.println("****\r\nServiceResult: "+serviceResult.error+". SKIPPING TEST. Full URL: "+fullURL);
\r
635 serviceResult = XmlReplayTransport.doDELETE(fullURL, authForTest, testID, fromTestID);
\r
637 serviceResult.fromTestID = fromTestID;
\r
638 results.add(serviceResult);
\r
640 } else if (method.equalsIgnoreCase("GET")){
\r
641 fullURL = fromTestID(fullURL, testNode, serviceResultsMap);
\r
642 serviceResult = XmlReplayTransport.doGET(fullURL, authForTest, testIDLabel);
\r
643 results.add(serviceResult);
\r
644 serviceResultsMap.put(testID, serviceResult);
\r
645 } else if (method.equalsIgnoreCase("LIST")){
\r
646 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
\r
647 String listQueryParams = ""; //TODO: empty for now, later may pick up from XML control file.
\r
648 serviceResult = XmlReplayTransport.doLIST(fullURL, listQueryParams, authForTest, testIDLabel);
\r
649 results.add(serviceResult);
\r
650 serviceResultsMap.put(testID, serviceResult);
\r
652 throw new Exception("HTTP method not supported by XmlReplay: "+method);
\r
655 serviceResult.testID = testID;
\r
656 serviceResult.fullURL = fullURL;
\r
657 serviceResult.auth = authForTest;
\r
658 serviceResult.method = method;
\r
659 if (expectedCodes.size()>0){
\r
660 serviceResult.expectedCodes = expectedCodes;
\r
662 if (Tools.isEmpty(serviceResult.testID)) serviceResult.testID = testIDLabel;
\r
663 if (Tools.isEmpty(serviceResult.testGroupID)) serviceResult.testGroupID = testGroupID;
\r
665 Node expectedLevel = testNode.selectSingleNode("response/expected");
\r
666 if (expectedLevel!=null){
\r
667 String level = expectedLevel.valueOf("@level");
\r
668 serviceResult.payloadStrictness = level;
\r
670 //=====================================================
\r
671 // ALL VALIDATION FOR ALL REQUESTS IS DONE HERE:
\r
672 //=====================================================
\r
673 boolean hasError = false;
\r
674 String vError = validateResponse(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);
\r
675 if (Tools.notEmpty(vError)){
\r
676 serviceResult.error = vError;
\r
677 serviceResult.failureReason = " : VALIDATION ERROR; ";
\r
680 if (hasError == false){
\r
681 hasError = ! serviceResult.gotExpectedResult();
\r
684 boolean doingAuto = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.auto);
\r
685 String serviceResultRow = serviceResult.dump(dump.dumpServiceResult, hasError)+"; time:"+(System.currentTimeMillis()-startTime);
\r
686 String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:"+testIDLabel+": ": "";
\r
688 report.addTestResult(serviceResult);
\r
690 if ( (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed)
\r
691 || (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.full) ){
\r
692 System.out.println("\r\n#---------------------#");
\r
694 System.out.println(leader+serviceResultRow+"\r\n");
\r
695 if (dump.payloads || (doingAuto&&hasError) ) {
\r
696 if (Tools.notBlank(serviceResult.requestPayload)){
\r
697 System.out.println("\r\n========== request payload ===============");
\r
698 System.out.println(serviceResult.requestPayload);
\r
699 System.out.println("==========================================\r\n");
\r
702 if (dump.payloads || (doingAuto&&hasError)) {
\r
703 if (Tools.notBlank(serviceResult.result)){
\r
704 System.out.println("\r\n========== response payload ==============");
\r
705 System.out.println(serviceResult.result);
\r
706 System.out.println("==========================================\r\n");
\r
709 } catch (Throwable t) {
\r
710 String msg = "ERROR: XmlReplay experienced an error in a test node: "+testNode+" Throwable: "+t;
\r
711 System.out.println(msg);
\r
712 System.out.println(Tools.getStackTrace(t));
\r
713 ServiceResult serviceResult = new ServiceResult();
\r
714 serviceResult.error = msg;
\r
715 serviceResult.failureReason = " : SYSTEM ERROR; ";
\r
716 results.add(serviceResult);
\r
719 if (Tools.isTrue(autoDeletePOSTS)&¶m_autoDeletePOSTS){
\r
720 autoDelete(serviceResultsMap, "default");
\r
724 //=== Now spit out the HTML report file ===
\r
725 File m = new File(controlFileName);
\r
726 String localName = m.getName();//don't instantiate, just use File to extract file name without directory.
\r
727 String reportName = localName+'-'+testGroupID+".html";
\r
728 File resultFile = FileTools.saveFile(getReportsDir(xmlReplayBaseDir), reportName, report.getPage(), true);
\r
729 if (resultFile!=null) {
\r
730 System.out.println("XmlReplay summary reports saved to directory: "+resultFile.getParent());
\r
731 System.out.println("XmlReplay summary report: "+resultFile.getCanonicalPath());
\r
732 String toc = report.getTOC(reportName);
\r
733 reportsList.add(toc);
\r
735 //================================
\r
740 //todo: move from xmlReplayBaseDir to "target/xmlReplayReports" dir.
\r
741 public static String getReportsDir(String basename){
\r
742 return Tools.glue(basename,"/","TEST-REPORTS");
\r
745 //======================== MAIN ===================================================================
\r
747 private static Options createOptions() {
\r
748 Options options = new Options();
\r
749 options.addOption("xmlReplayBaseDir", true, "default/basedir");
\r
753 public static String usage(){
\r
754 String result = "org.collectionspace.services.IntegrationTests.xmlreplay.XmlReplay {args}\r\n"
\r
755 +" -xmlReplayBaseDir <dir> \r\n"
\r
756 +" You may also override these with system args, e.g.: \r\n"
\r
757 +" -DxmlReplayBaseDir=/path/to/dir \r\n"
\r
758 +" These may also be passed in via the POM.\r\n"
\r
759 +" You can also set these system args, e.g.: \r\n"
\r
760 +" -DtestGroupID=<oneID> \r\n"
\r
761 +" -DtestID=<one TestGroup ID>"
\r
762 +" -DautoDeletePOSTS=<true|false> \r\n"
\r
763 +" (note: -DautoDeletePOSTS won't force deletion if set to false in control file.";
\r
767 private static String opt(CommandLine line, String option){
\r
769 String fromProps = System.getProperty(option);
\r
770 if (Tools.notEmpty(fromProps)){
\r
776 result = line.getOptionValue(option);
\r
777 if (result == null){
\r
783 public static void main(String[]args) throws Exception {
\r
784 Options options = createOptions();
\r
785 //System.out.println("System CLASSPATH: "+prop.getProperty("java.class.path", null));
\r
786 CommandLineParser parser = new GnuParser();
\r
788 // parse the command line arguments
\r
789 CommandLine line = parser.parse(options, args);
\r
791 String xmlReplayBaseDir = opt(line, "xmlReplayBaseDir");
\r
792 String testGroupID = opt(line, "testGroupID");
\r
793 String testID = opt(line, "testID");
\r
794 String autoDeletePOSTS = opt(line, "autoDeletePOSTS");
\r
795 String dumpResults = opt(line, "dumpResults");
\r
796 String controlFilename = opt(line, "controlFilename");
\r
797 String xmlReplayMaster = opt(line, "xmlReplayMaster");
\r
799 xmlReplayBaseDir = Tools.fixFilename(xmlReplayBaseDir);
\r
800 controlFilename = Tools.fixFilename(controlFilename);
\r
802 boolean bAutoDeletePOSTS = true;
\r
803 if (Tools.notEmpty(autoDeletePOSTS)) {
\r
804 bAutoDeletePOSTS = Tools.isTrue(autoDeletePOSTS);
\r
806 boolean bDumpResults = false;
\r
807 if (Tools.notEmpty(dumpResults)) {
\r
808 bDumpResults = Tools.isTrue(autoDeletePOSTS);
\r
810 if (Tools.isEmpty(xmlReplayBaseDir)){
\r
811 System.err.println("ERROR: xmlReplayBaseDir was not specified.");
\r
814 File f = new File(Tools.glue(xmlReplayBaseDir, "/", controlFilename));
\r
815 if (Tools.isEmpty(xmlReplayMaster) && !f.exists()){
\r
816 System.err.println("Control file not found: "+f.getCanonicalPath());
\r
819 File fMaster = new File(Tools.glue(xmlReplayBaseDir, "/", xmlReplayMaster));
\r
820 if (Tools.notEmpty(xmlReplayMaster) && !fMaster.exists()){
\r
821 System.err.println("Master file not found: "+fMaster.getCanonicalPath());
\r
825 String xmlReplayBaseDirResolved = (new File(xmlReplayBaseDir)).getCanonicalPath();
\r
826 System.out.println("XmlReplay ::"
\r
827 + "\r\n xmlReplayBaseDir: "+xmlReplayBaseDir
\r
828 + "\r\n xmlReplayBaseDir(resolved): "+xmlReplayBaseDirResolved
\r
829 + "\r\n controlFilename: "+controlFilename
\r
830 + "\r\n xmlReplayMaster: "+xmlReplayMaster
\r
831 + "\r\n testGroupID: "+testGroupID
\r
832 + "\r\n testID: "+testID
\r
833 + "\r\n autoDeletePOSTS: "+bAutoDeletePOSTS
\r
834 + (Tools.notEmpty(xmlReplayMaster)
\r
835 ? ("\r\n will use master file: "+fMaster.getCanonicalPath())
\r
836 : ("\r\n will use control file: "+f.getCanonicalPath()) )
\r
839 if (Tools.notEmpty(xmlReplayMaster)){
\r
840 if (Tools.notEmpty(controlFilename)){
\r
841 System.out.println("WARN: controlFilename: "+controlFilename+" will not be used because master was specified. Running master: "+xmlReplayMaster);
\r
843 XmlReplay replay = new XmlReplay(xmlReplayBaseDirResolved);
\r
844 replay.readOptionsFromMasterConfigFile(xmlReplayMaster);
\r
845 replay.setAutoDeletePOSTS(bAutoDeletePOSTS);
\r
846 Dump dumpFromMaster = replay.getDump();
\r
847 dumpFromMaster.payloads = Tools.isTrue(dumpResults);
\r
848 replay.setDump(dumpFromMaster);
\r
849 replay.runMaster(xmlReplayMaster, false); //false, because we already just read the options, and override a few.
\r
851 Dump dump = getDumpConfig();
\r
852 dump.payloads = Tools.isTrue(dumpResults);
\r
853 List<String> reportsList = new ArrayList<String>();
\r
854 runXmlReplayFile(xmlReplayBaseDirResolved, controlFilename, testGroupID, testID, createResultsMap(), bAutoDeletePOSTS, dump, "", null, reportsList);
\r
855 System.out.println("DEPRECATED: reportsList is generated, but not dumped: "+reportsList.toString());
\r
857 } catch (ParseException exp) {
\r
858 // oops, something went wrong
\r
859 System.err.println("Cmd-line parsing failed. Reason: " + exp.getMessage());
\r
860 System.err.println(usage());
\r
861 } catch (Exception e) {
\r
862 System.out.println("Error : " + e.getMessage());
\r
863 e.printStackTrace();
\r