ATTAcomm.js (31437B)
1 /* globals Promise, window, done, assert_true, on_event, promise_test */ 2 3 /** 4 * Creates an ATTAcomm object. If the parameters are supplied 5 * it sets up event listeners to send the test data to an ATTA if one 6 * is available. If the ATTA does not respond, it will assume the test 7 * is being done manually and the results are being entered in the 8 * parent test window. 9 * 10 * @constructor 11 * @param {object} params 12 * @param {string} [params.test] - object containing JSON test definition 13 * @param {string} [params.testFile] - URI of a file with JSON test definition 14 * @param {string} params.ATTAuri - URI to use to exercise the window 15 * @event DOMContentLoaded Calls go once DOM is fully loaded 16 * @returns {object} Reference to the new object 17 * 18 */ 19 20 function ATTAcomm(params) { 21 'use strict'; 22 23 this.Params = null; // parameters passed in 24 this.Promise = null; // master Promise that resolves when intialization is complete 25 this.Properties = null; // testharness_properties from the opening window 26 this.Tests = null; // test object being processed 27 this.testName = ""; // name of test being run 28 this.log = ""; // a buffer to capture log information for debugging 29 this.startReponse = {}; // startTest response will go in here for debugging 30 31 this.loading = true; 32 33 this.timeout = 5000; 34 35 var pending = [] ; 36 37 // set up in case DOM finishes loading early 38 pending.push(new Promise(function(resolve) { 39 on_event(document, "DOMContentLoaded", function() { 40 resolve(true); 41 }.bind(this)); 42 }.bind(this))); 43 44 // if we are under runner, then there are props in the parent window 45 // 46 // if "output" is set in that, then pause at the end of running so the output 47 // can be analyzed. @@@TODO@@@ 48 if (window && window.opener && window.opener.testharness_properties) { 49 this.Properties = window.opener.testharness_properties; 50 } 51 52 this.Params = params; 53 54 if (this.Params.hasOwnProperty("ATTAuri")) { 55 this.ATTAuri = this.Params.ATTAuri; 56 } else { 57 this.ATTAuri = "http://localhost:4119"; 58 } 59 60 if (this.Params.hasOwnProperty("title")) { 61 this.testName = this.Params.title; 62 } 63 64 // start by loading the test (it might be inline, but 65 // loadTest deals with that 66 pending.push(this.loadTest(params) 67 .then(function(tests) { 68 // if the test is NOT an object, turn it into one 69 if (typeof tests === 'string') { 70 tests = JSON.parse(tests) ; 71 } 72 73 this.Tests = tests; 74 75 }.bind(this))); 76 77 this.Promise = new Promise(function(resolve, reject) { 78 // once the DOM and the test is loaded... set us up 79 Promise.all(pending) 80 .then(function() { 81 // Everything is loaded 82 this.loading = false ; 83 // run the automated tests (or setup for manual testing) 84 this.go(); 85 resolve(this); 86 }.bind(this)) 87 .catch(function(err) { 88 // loading the components failed somehow - report the errors and mark the test failed 89 test( function() { 90 assert_true(false, "Loading of test components failed: " +JSON.stringify(err)) ; 91 }, "Loading test components"); 92 this.dumpLog(); 93 done() ; 94 reject("Loading of test components failed: "+JSON.stringify(err)); 95 return ; 96 }.bind(this)); 97 }.bind(this)); 98 99 return this; 100 } 101 102 ATTAcomm.prototype = { 103 104 /** 105 * go sets up the connection to the ATTA 106 * 107 * If that succeeds and the tests in this test file have methods for 108 * the API supported by the ATTA, then it automatically runs those tests. 109 * 110 * Otherwise it sets up for manualt testing. 111 */ 112 go: function() { 113 'use strict'; 114 // everything is ready. Let's talk to the ATTA 115 this.startTest().then(function(res) { 116 117 // start was successful - iterate over steps 118 var API = res.body.API; 119 120 var subtestsForAPI = false; 121 122 // check main and potentially nested lists of tests for 123 // tests with this API. If any step is missing this API 124 // mapping, then we need to be manual 125 this.Tests.forEach(function(subtest) { 126 if (subtest.hasOwnProperty("test") && 127 subtest.test.hasOwnProperty(API)) { 128 // there is at least one subtest for this API so 129 // this is a test that needs to be looked at by an atta 130 subtestsForAPI = true; 131 } else if (Array.isArray(subtest)) { 132 subtest.forEach(function(st) { 133 if (st.hasOwnProperty("test") && 134 st.test.hasOwnProperty(API)) { 135 subtestsForAPI = true; 136 } 137 }); 138 } 139 }); 140 141 if (subtestsForAPI) { 142 this.runTests(API, this.Tests) 143 .then(function() { 144 // the tests all ran; close it out 145 this.endTest().then(function() { 146 this.dumpLog(); 147 done(); 148 }.bind(this)); 149 }.bind(this)) 150 .catch(function(err) { 151 this.endTest().then(function() { 152 this.dumpLog(); 153 done(); 154 }.bind(this)); 155 }.bind(this)); 156 } else { 157 // we don't know this API for this test 158 // but we ARE talking to an ATTA; skip this test 159 this.dumpLog(); 160 if (window.opener && window.opener.completion_callback) { 161 window.opener.completion_callback([], { status: 3, message: "No steps for AT API " + API } ); 162 } else { 163 done(); 164 } 165 // this.setupManualTest("Unknown AT API: " + API); 166 } 167 }.bind(this)) 168 .catch(function(res) { 169 // startTest failed so just sit and wait for a manual test to occur 170 if (res.timeout || res.status === 102) { 171 this.setupManualTest("No response from ATTA at " + this.ATTAuri); 172 } else if (res.status === 200 ) { 173 this.setupManualTest(res.message); 174 } else if (res.statusText === "No response from ATTA") { 175 this.setupManualTest(""); 176 } else { 177 this.setupManualTest("Error from ATTA: " + res.status + ": " + res.statusText); 178 } 179 }.bind(this)); 180 }, 181 182 runTests: function(API, collection) { 183 // this method returns a promise 184 185 return new Promise(function(resolve, reject) { 186 // accumulate promises; complete when done 187 var pending = []; 188 var testCount = 0; 189 190 this.sendEvents(API, collection) 191 .then(function(eventStatus) { 192 193 /* Loop strategy... 194 * 195 * If the step is a 'test' then push it into the pending queue as a promise 196 * 197 * If the step is anything else, then if there is anything in pending, wait on it 198 * Once it resolves, clear the queue and then execute the other step. 199 * 200 */ 201 collection.forEach(function(subtest) { 202 // what "type" of step in the sequence is this? 203 var theType = "test" ; 204 if (Array.isArray(subtest)) { 205 // it is a group 206 Promise.all(pending).then(function() { 207 pending = []; 208 // recursively run the tests 209 pending.push(this.runTests(API, subtest)); 210 }.bind(this)); 211 } else if (subtest.hasOwnProperty("type")) { 212 theType = subtest.type; 213 } 214 testCount++; 215 if (theType === "test") { 216 // this is a set of assertions that should be evaluated 217 pending.push(this.runTest(testCount, API, subtest)); 218 } else if (theType === "script") { 219 Promise.all(pending).then(function() { 220 pending = []; 221 // execute the script 222 this.runScript(testCount, subtest); 223 }.bind(this)); 224 } else if (theType === "attribute") { 225 Promise.all(pending).then(function() { 226 pending = []; 227 // raise the event 228 this.handleAttribute(testCount, subtest); 229 }.bind(this)); 230 // } else { 231 } else if (theType === "event") { 232 Promise.all(pending).then(function() { 233 pending = []; 234 // raise the event 235 this.raiseEvent(testCount, subtest); 236 }.bind(this)); 237 // } else { 238 } 239 }.bind(this)); 240 241 Promise.all(pending) 242 .then(function() { 243 // this collection all ran 244 if (eventStatus !== "NOEVENTS") { 245 // there were some events at the beginning 246 this.sendStopListen().then(function() { 247 resolve(true); 248 }); 249 } else { 250 resolve(true); 251 } 252 }.bind(this)); 253 }.bind(this)); 254 }.bind(this)); 255 }, 256 257 setupManualTest: function(message) { 258 // if we determine the test should run manually, then expose all of the conditions that are 259 // in the TEST data structure so that a human can to the inspection and calculate the result 260 // 261 'use strict'; 262 263 var ref = document.getElementById("manualMode"); 264 if (ref) { 265 // we have a manualMode block. Populate it 266 var content = "<h2>Manual Mode Enabled</h2><p>"+message+"</p>"; 267 if (this.Tests.hasOwnProperty("description")) { 268 content += "<p>" + this.Tests.description + "</p>"; 269 } 270 var theTable = "<table id='steps'><tr><th>Step</th><th>Type</th><th>Element ID</th><th>Assertions</th></tr>"; 271 this.Tests.forEach(function(subtest) { 272 var type = "test"; 273 if (subtest.hasOwnProperty("type")) { 274 type = subtest.type; 275 } 276 var id = "" ; 277 if (subtest.hasOwnProperty("element")) { 278 id = subtest.element; 279 } 280 theTable += "<tr><td class='step'>" + subtest.title +"</td>"; 281 theTable += "<td class='type'>" + type + "</td>"; 282 theTable += "<td class='element'>" + id +"</td>"; 283 284 // now what do we put over here? depends on the type 285 if (type === "test") { 286 // it is a test; dump the assertions 287 theTable += "<td>" + this.buildAssertionTable(subtest.test) + "</td>"; 288 } else if (type === "attribute" ) { 289 if (subtest.hasOwnProperty("attribute") && subtest.hasOwnProperty("value") && subtest.hasOwnProperty("element")) { 290 if (subtest.value === "none") { 291 theTable += "<td>Remove attribute <code>" + subtest.attribute + "</code> from the element with ID <code>" + subtest.element + "</code></td>"; 292 } else { 293 theTable += "<td>Set attribute <code>" + subtest.attribute + "</code> on the element with ID <code>" + subtest.element + "</code> to the value <code>" + subtest.value + "</code></td>"; 294 } 295 } 296 } else if (type === "event" ) { 297 // it is some events 298 if (subtest.hasOwnProperty("event") && subtest.hasOwnProperty("element")) { 299 theTable += "<td>Send event <code>" + subtest.event + "</code> to the element with ID <code>" + subtest.element + "</code></td>"; 300 } 301 } else if (type === "script" ) { 302 // it is a script fragment 303 theTable += "<td>Script: " + subtest.script + "</td>"; 304 } else { 305 theTable += "<td>Unknown type: " + type + "</td>"; 306 } 307 theTable += "</tr>"; 308 309 310 }.bind(this)); 311 312 theTable += "</table>"; 313 ref.innerHTML = content + theTable ; 314 } 315 }, 316 317 buildAssertionTable: function(asserts) { 318 "use strict"; 319 var output = "<table class='api'><tr><th>API Name</th><th colspan='4'>Assertions</th></tr>"; 320 var APIs = [] ; 321 for (var k in asserts) { 322 if (asserts.hasOwnProperty(k)) { 323 APIs.push(k); 324 } 325 } 326 327 APIs.sort().forEach(function(theAPI) { 328 var rows = asserts[theAPI] ; 329 var height = rows.length; 330 output += "<tr><td rowspan='" + height + "' class='apiName'>"+theAPI+"</td>"; 331 var lastRow = rows.length - 1; 332 rows.forEach(function(theRow, index) { 333 var span = 4 - theRow.length; 334 var colspan = span ? " colspan='"+span+"'" : ""; 335 theRow.forEach(function(item) { 336 output += "<td" + colspan + ">" + item + "</td>"; 337 }); 338 output += "</tr>"; 339 if (index < lastRow) { 340 output += "<tr>"; 341 } 342 }); 343 }); 344 345 output += "</table>"; 346 return output; 347 }, 348 349 // eventList - find the events for an API 350 // 351 // @param {string} API 352 // @param {array} collection - a collection of tests 353 // @returns {array} list of event names 354 355 eventList: function(API, collection) { 356 var eventHash = {}; 357 358 if (!API || API === "") { 359 return []; 360 } 361 362 collection.forEach(function(subtest) { 363 if (subtest.hasOwnProperty("test") && 364 subtest.test.hasOwnProperty(API)) { 365 // this is a subtest for this API; look at the events 366 subtest.test[API].forEach(function(assert) { 367 // look for event names 368 if (assert[0] === "event" && assert[1] === "type" && assert[2] === "is") { 369 eventHash[assert[3]] = 1; 370 } 371 }); 372 } 373 }); 374 375 return Object.keys(eventHash); 376 }, 377 378 // handleAttribute - set or clear an attribute 379 /** 380 * @param {integer} testNum - The subtest number 381 * @param {object} subtest - attribute information to set 382 */ 383 handleAttribute: function(testNum, subtest) { 384 "use strict"; 385 if (subtest) { 386 if (subtest.hasOwnProperty("attribute") && subtest.hasOwnProperty("element") && subtest.hasOwnProperty("value")) { 387 // update an attribute 388 try { 389 var node = document.getElementById(subtest.element); 390 if (node) { 391 if (subtest.value === "none") { 392 // remove this attribute 393 node.removeAttribute(subtest.attribute); 394 } else if (subtest.value === '""') { 395 node.setAttribute(subtest.attribute, ""); 396 } else if (subtest.value.match(/^"/) ) { 397 var v = subtest.value; 398 v = v.replace(/^"/, ''); 399 v = v.replace(/"$/, ''); 400 node.setAttribute(subtest.attribute, v); 401 } else { 402 node.setAttribute(subtest.attribute, subtest.value); 403 } 404 } 405 } 406 catch (e) { 407 test(function() { 408 assert_true(false, "Subtest attribute failed to update: " +e); 409 }, "Attribute subtest " + testNum); 410 } 411 } else { 412 test(function() { 413 var err = ""; 414 if (!subtest.hasOwnProperty("attribute")) { 415 err += "Attribute subtest has no attribute property; "; 416 } else if (!subtest.hasOwnProperty("value")) { 417 err += "Attribute subtest has no value property; "; 418 } else if (!subtest.hasOwnProperty("element")) { 419 err += "Attribute subtest has no element property; "; 420 } 421 assert_true(false, err); 422 }, "Attribute subtest " + testNum ); 423 } 424 } 425 return; 426 }, 427 428 429 430 // raiseEvent - throw an event at an item 431 /** 432 * @param {integer} testNum - The subtest number 433 * @param {object} subtest - event information to throw 434 */ 435 raiseEvent: function(testNum, subtest) { 436 "use strict"; 437 var evt; 438 if (subtest) { 439 var kp = function(target, key) { 440 evt = document.createEvent("KeyboardEvent"); 441 evt.initKeyEvent ("keypress", true, true, window, 442 0, 0, 0, 0, 0, "e".charCodeAt(0)); 443 target.dispatchEvent(evt); 444 }; 445 if (subtest.hasOwnProperty("event") && subtest.hasOwnProperty("element")) { 446 // throw an event 447 try { 448 var node = document.getElementById(subtest.element); 449 if (node) { 450 if (subtest.event === "focus") { 451 node.focus(); 452 } else if (subtest.event === "select") { 453 node.click(); 454 } else if (subtest.event.startsWith('key:')) { 455 var key = subtest.event.replace('key:', ''); 456 evt = new KeyboardEvent("keypress", { "key": key}); 457 node.dispatchEvent(evt); 458 } else { 459 evt = new Event(subtest.element); 460 node.dispatchEvent(evt); 461 } 462 } 463 } 464 catch (e) { 465 test(function() { 466 assert_true(false, "Subtest event failed to dispatch: " +e); 467 }, "Event subtest " + testNum); 468 } 469 } else { 470 test(function() { 471 var err = ""; 472 if (!subtest.hasOwnProperty("event")) { 473 err += "Event subtest has no event property; "; 474 } else if (!subtest.hasOwnProperty("element")) { 475 err += "Event subtest has no element property; "; 476 } 477 assert_true(false, err); 478 }, "Event subtest " + testNum ); 479 } 480 } 481 return; 482 }, 483 484 // runScript - run a script in the context of the window 485 /** 486 * @param {integer} testNum - The subtest number 487 * @param {object} subtest - script and related information 488 */ 489 runScript: function(testNum, subtest) { 490 "use strict"; 491 if (subtest) { 492 if (subtest.hasOwnProperty("script") && typeof subtest.script === "string") { 493 try { 494 /* jshint evil:true */ 495 eval(subtest.script); 496 } 497 catch (e) { 498 test(function() { 499 assert_true(false, "Subtest script " + subtest.script + " failed to evaluate: " +e); 500 }, "Event subtest " + testNum); 501 } 502 } else { 503 test(function() { 504 assert_true(false, "Event subtest has no script property"); 505 }, "Event subtest " + testNum ); 506 } 507 } 508 return; 509 }, 510 511 // runTest - process subtest 512 /** 513 * @param {integer} testNum - The subtest number 514 * @param {string} API - name of the API being tested 515 * @param {object} subtest - a subtest to run; contains 'title', 'element', and 516 * 'test array' 517 * @returns {Promise} - a Promise that resolves when the test completes 518 */ 519 runTest: function(testNum, API, subtest) { 520 'use strict'; 521 522 var data = { 523 "title" : subtest.title, 524 "id" : subtest.element, 525 "data": this.normalize(subtest.test[API]) 526 }; 527 528 return new Promise(function(resolve) { 529 var ANNO = this; 530 if (subtest.test[API]) { 531 // we actually have a test to run 532 promise_test(function() { 533 // force a resolve of the promise regardless 534 this.add_cleanup(function() { resolve(true); }); 535 return ANNO.sendTest(data) 536 .then(function(res) { 537 if (typeof res.body === "object" && res.body.hasOwnProperty("status")) { 538 // we got some sort of response 539 if (res.body.status === "OK") { 540 // the test ran - yay! 541 var messages = ""; 542 var thisResult = null; 543 var theLog = ""; 544 var assertionCount = 0; 545 res.body.results.forEach( function (a) { 546 if (typeof a === "object") { 547 // we have a result for this assertion 548 // first, what is the assertion? 549 var aRef = data.data[assertionCount]; 550 var assertionText = '"' + aRef.join(" ") +'"'; 551 552 if (a.hasOwnProperty("log") && a.log !== null && a.log !== '' ) { 553 // there is log data - save it 554 theLog += "\n--- Assertion " + assertionCount + " ---"; 555 theLog += "\nAssertion: " + assertionText + "\nLog data: "+a.log ; 556 } 557 558 // is there a message? 559 var theMessage = ""; 560 if (a.hasOwnProperty("message")) { 561 theMessage = a.message; 562 } 563 if (!a.hasOwnProperty("result")) { 564 messages += "ATTA did not report a result " + theMessage + "; "; 565 } else if (a.result === "ERROR") { 566 messages += "ATTA reported ERROR with message: " + theMessage + "; "; 567 } else if (a.result === "FAIL") { 568 thisResult = false; 569 messages += assertionText + " failed " + theMessage + "; "; 570 } else if (a.result === "PASS" && thisResult === null) { 571 // if we got a pass and there was no other result thus far 572 // then we are passing 573 thisResult = true; 574 } 575 } 576 assertionCount++; 577 }); 578 if (theLog !== "") { 579 ANNO.saveLog("runTest", theLog, subtest); 580 } 581 if (thisResult !== null) { 582 assert_true(thisResult, messages); 583 } else { 584 assert_true(false, "ERROR: No results reported from ATTA; " + messages); 585 } 586 } else if (res.body.status === "ERROR") { 587 assert_true(false, "ATTA returned ERROR with message: " + res.body.statusText); 588 } else { 589 assert_true(false, "ATTA returned unknown status " + res.body.status + " with message: " + res.body.statusText); 590 } 591 } else { 592 // the return wasn't an object! 593 assert_true(false, "ATTA failed to return a result object: returned: "+JSON.stringify(res)); 594 } 595 }); 596 }, subtest.name ); 597 } else { 598 // there are no test steps for this API. fake a subtest result 599 promise_test(function() { 600 // force a resolve of the promise regardless 601 this.add_cleanup(function() { resolve(true); }); 602 return new Promise(function(innerResolve) { 603 innerResolve(true); 604 }) 605 .then(function(res) { 606 var theLog = "\nSUBTEST NOTRUN: No assertions for API " + API + "\n"; 607 if (theLog !== "") { 608 ANNO.saveLog("runTest", theLog, subtest); 609 } 610 assert_false(true, "NOTRUN: No assertion for API " + API); 611 }); 612 }, subtest.name ); 613 } 614 }.bind(this)); 615 }, 616 617 // loadTest - load a test from an external JSON file 618 // 619 // returns a promise that resolves with the contents of the 620 // test 621 622 loadTest: function(params) { 623 'use strict'; 624 625 if (params.hasOwnProperty('stepFile')) { 626 // the test is referred to by a file name 627 return this._fetch("GET", params.stepFile); 628 } // else 629 return new Promise(function(resolve, reject) { 630 if (params.hasOwnProperty('steps')) { 631 resolve(params.steps); 632 } else { 633 reject("Must supply a 'steps' or 'stepFile' parameter"); 634 } 635 }); 636 }, 637 638 /* dumpLog - put log information into the log div on the page if it exists 639 */ 640 641 dumpLog: function() { 642 'use strict'; 643 if (this.log !== "") { 644 var ref = document.getElementById("ATTAmessages"); 645 if (ref) { 646 // we have a manualMode block. Populate it 647 var content = "<h2>Logging information recorded</h2>"; 648 if (this.startResponse && this.startResponse.hasOwnProperty("API")) { 649 content += "<h3>ATTA Information</h3>"; 650 content += "<pre>"+JSON.stringify(this.startResponse, null, " ")+"</pre>"; 651 } 652 content += "<textarea rows='50' style='width:100%'>"+this.log+"</textarea>"; 653 ref.innerHTML = content ; 654 } 655 } 656 }, 657 658 /* saveLog - capture logging information so that it can be displayed on the page after testing is complete 659 * 660 * @param {string} caller name 661 * @param {string} log message 662 * @param {object} subtest 663 */ 664 665 saveLog: function(caller, message, subtest) { 666 'use strict'; 667 668 if (typeof message === "string" && message !== "") { 669 this.log += "============================================================\n"; 670 this.log += "Message from " + caller + "\n"; 671 if (subtest && typeof subtest === "object") { 672 var API = this.startResponse.API; 673 this.log += "\n SUBTEST TITLE: " + subtest.title; 674 this.log += "\n SUBTEST ELEMENT: " + subtest.element; 675 this.log += "\n SUBTEST DATA: " + JSON.stringify(subtest.test[API]); 676 this.log += "\n\n"; 677 } 678 this.log += message; 679 } 680 return; 681 }, 682 683 // startTest - send the test start message 684 // 685 // @returns {Promise} resolves if the start is successful, or rejects with 686 687 startTest: function() { 688 'use strict'; 689 690 return new Promise(function(resolve, reject) { 691 var params = { 692 test: this.testName || window.title, 693 url: document.location.href 694 }; 695 696 this._fetch("POST", this.ATTAuri + "/start", null, params) 697 .then(function(res) { 698 if (res.body.hasOwnProperty("status")) { 699 if (res.body.status === "READY") { 700 this.startResponse = res.body; 701 if (res.body.hasOwnProperty("log")) { 702 // there is some logging data - capture it 703 this.saveLog("startTest", res.body.log); 704 } 705 // the system is ready for us - is it really? 706 if (res.body.hasOwnProperty("API")) { 707 resolve(res); 708 } else { 709 res.message = "No API in response from ATTA"; 710 reject(res); 711 } 712 } else { 713 // the system reported something else - fail out with the statusText as a result 714 res.message = "ATTA reported an error: " + res.body.statusText; 715 reject(res); 716 } 717 } else { 718 res.message = "ATTA did not report a status"; 719 reject(res); 720 } 721 }.bind(this)) 722 .catch(function(res) { 723 reject(res); 724 }); 725 }.bind(this)); 726 }, 727 728 // sendEvents - send the list of events the ATTA needs to listen for 729 // 730 // @param {string} API 731 // @param {array} collection - a list of tests 732 // @returns {Promise} resolves if the message is successful, or rejects with 733 734 sendEvents: function(API, collection) { 735 'use strict'; 736 737 return new Promise(function(resolve, reject) { 738 var eList = this.eventList(API, collection) ; 739 if (eList && eList.length) { 740 var params = { 741 events: eList 742 }; 743 744 this._fetch("POST", this.ATTAuri + "/startlisten", null, params) 745 .then(function(res) { 746 if (res.body.hasOwnProperty("status")) { 747 if (res.body.status === "READY") { 748 if (res.body.hasOwnProperty("log")) { 749 // there is some logging data - capture it 750 this.saveLog("sendEvents", res.body.log); 751 } 752 resolve(res.body.status); 753 } else { 754 // the system reported something else - fail out with the statusText as a result 755 res.message = "ATTA reported an error: " + res.body.statusText; 756 reject(res); 757 } 758 } else { 759 res.message = "ATTA did not report a status"; 760 reject(res); 761 } 762 }.bind(this)) 763 .catch(function(res) { 764 reject(res); 765 }); 766 } else { 767 // there are no events 768 resolve("NOEVENTS"); 769 } 770 }.bind(this)); 771 }, 772 773 sendStopListen: function() { 774 'use strict'; 775 776 return this._fetch("POST", this.ATTAuri + "/stoplisten", null, null); 777 }, 778 779 // sendTest - send test data to an ATTA and wait for a response 780 // 781 // returns a promise that resolves with the results of the test 782 783 sendTest: function(testData) { 784 'use strict'; 785 786 if (typeof testData !== "string") { 787 testData = JSON.stringify(testData); 788 } 789 var ret = this._fetch("POST", this.ATTAuri + "/test", null, testData, true); 790 ret.then(function(res) { 791 if (res.body.hasOwnProperty("log")) { 792 // there is some logging data - capture it 793 this.saveLog("sendTest", res.body.log); 794 } 795 }.bind(this)); 796 return ret; 797 }, 798 799 endTest: function() { 800 'use strict'; 801 802 return this._fetch("GET", this.ATTAuri + "/end"); 803 }, 804 805 /* normalize - ensure subtest data conforms to ATTA spec 806 */ 807 808 normalize: function( data ) { 809 'use strict'; 810 811 var ret = [] ; 812 813 if (data) { 814 data.forEach(function(assert) { 815 var normal = [] ; 816 // ensure if there is a value list it is compressed 817 if (Array.isArray(assert)) { 818 // we have an array 819 normal[0] = assert[0]; 820 normal[1] = assert[1]; 821 normal[2] = assert[2]; 822 if ("string" === typeof assert[3] && assert[3].match(/^\[.*\]$/)) { 823 // it is a string and matches the valuelist pattern 824 normal[3] = assert[3].replace(/, +/, ','); 825 } else { 826 normal[3] = assert[3]; 827 } 828 ret.push(normal); 829 } else { 830 ret.push(assert); 831 } 832 }); 833 } 834 return ret; 835 }, 836 837 // _fetch - return a promise after sending data 838 // 839 // Resolves with the returned information in a structure 840 // including: 841 // 842 // xhr - a raw xhr object 843 // headers - an array of headers sent in the request 844 // status - the status code 845 // statusText - the text of the return status 846 // text - raw returned data 847 // body - an object parsed from the returned content 848 // 849 850 _fetch: function (method, url, headers, content, parse) { 851 'use strict'; 852 if (method === null || method === undefined) { 853 method = "GET"; 854 } 855 if (parse === null || parse === undefined) { 856 parse = true; 857 } 858 if (headers === null || headers === undefined) { 859 headers = []; 860 } 861 862 863 // note that this Promise always resolves - there is no reject 864 // condition 865 866 return new Promise(function (resolve, reject) { 867 var xhr = new XMLHttpRequest(); 868 869 // this gets returned when the request completes 870 var resp = { 871 xhr: xhr, 872 headers: null, 873 status: 0, 874 statusText: "", 875 body: null, 876 text: "" 877 }; 878 879 xhr.open(method, url); 880 881 // headers? 882 headers.forEach(function(ref) { 883 xhr.setRequestHeader(ref[0], ref[1]); 884 }); 885 886 //if (this.timeout) { 887 // xhr.timeout = this.timeout; 888 //} 889 890 xhr.ontimeout = function() { 891 resp.timeout = this.timeout; 892 resolve(resp); 893 }; 894 895 xhr.onerror = function() { 896 if (this.status) { 897 resp.status = this.status; 898 resp.statusText = xhr.statusText; 899 } else if (this.status === 0) { 900 resp.status = 0; 901 resp.statusText = "No response from ATTA"; 902 } 903 reject(resp); 904 }; 905 906 xhr.onload = function () { 907 resp.status = this.status; 908 if (this.status >= 200 && this.status < 300) { 909 var d = xhr.response; 910 // return the raw text of the response 911 resp.text = d; 912 // we have it; what is it? 913 if (parse) { 914 try { 915 d = JSON.parse(d); 916 resp.body = d; 917 } 918 catch(err) { 919 resp.body = null; 920 } 921 } 922 resolve(resp); 923 } else { 924 reject({ 925 status: this.status, 926 statusText: xhr.statusText 927 }); 928 } 929 }; 930 931 if (content !== null && content !== undefined) { 932 if ("object" === typeof(content)) { 933 xhr.send(JSON.stringify(content)); 934 } else if ("function" === typeof(content)) { 935 xhr.send(content()); 936 } else if ("string" === typeof(content)) { 937 xhr.send(content); 938 } 939 } else { 940 xhr.send(); 941 } 942 }); 943 }, 944 945 }; 946 947 // vim: set ts=2 sw=2: