window.onload = function() {
nf.Testing.init();
if(navigator.userAgent.match(/msie/i)) {
alert('TestCentre uses advanced software features not currently supported ' +
'by Internet Explorer. We recommend Firefox or Safari as alternatives.')
}
};
// test controller class nf.Testing = {
TEST_PROMPT: 'Enter a descriptive name for this test',
DEFAULT_TEST: "# Netfira WebConnect Test Script\n" +
"\n" +
"# Please refer to WebConnect documentation for script syntax.",
DEFAULT_TEST_NAME: 'Untitled Test',
DEFAULT_ORDER_TIMEOUT: 60,
isBusy: null,
isTesting: null,
busy: function(isBusy) {
this.isBusy = !!isBusy;
nf.className(document.body, 'busy', isBusy ? 1 : -1);
},
tests: [],
activeTestIndex: -1,
activeTest: function() {
return this.tests.length
? this.tests[this.activeTestIndex]
: null;
},
init: function() {
this.busy(true);
this.testList = new nf.ListView(nf('#tests'));
this.results.list = new nf.ListView(nf('#actions'));
nf.api.get('info', function(x) {
this.owner.receiveSchema(x.info.schema);
this.owner.locale = x.info.locale;
this.owner.allowCustomFields = x.info.customFields;
}).owner = this;
this.results.rootElement = nf('#results');
document.getElementsByTagName('head')[0].appendChild(nf(
'<link>',
null,
{
rel: 'shortcut icon',
type: 'image/gif',
href: 'data:image/gif;base64,' + this.icon
}
));
},
receiveSchema: function(schema) {
this.schema = schema;
// reverse lookup
var i;
this.singular = {};
for(i in schema) {
this.singular[schema[i].singular] = i;
}
nf.api.get('settings/testScripts', function(x) {
this.owner.receiveTests(x)
}).owner = this;
},
receiveTests: function(r) {
var i;
if(r instanceof Array) {
for(i = 0; i < r.length; i++) {
this.addTest(new this.Test(r[i].name, r[i].source));
}
this.showTest(0);
} else {
this.create();
}
this.busy(false);
},
rename: function() {
if(this.isBusy || this.isTesting) {
return;
}
var test = this.activeTest(),
a = test.listItem.firstChild,
newName = prompt(this.TEST_PROMPT, test.name);
if(!newName) {
return;
}
test.name = newName;
a.innerHTML = '';
nf('<', a, newName);
nf('#source').focus();
},
del: function() {
if(this.isBusy || this.isTesting) {
return;
}
if(this.tests.length == 1) {
alert("You can't delete the only remaining test. Please add another first.");
}
else if(confirm("Delete test '" + this.activeTest().name + "'?")) {
this.testList.removeItem(this.activeTest().listItem);
this.tests.splice(this.activeTestIndex, 1);
var i = this.activeTestIndex;
this.activeTestIndex = -1;
if(i == this.tests.length) {
i--;
}
this.showTest(i);
}
},
add: function() {
if(this.isBusy || this.isTesting) {
return;
}
var x = prompt(this.TEST_PROMPT, this.DEFAULT_TEST_NAME);
if(x) {
this.create(x);
}
},
create: function(name) {
this.addTest(new this.Test(name || this.DEFAULT_TEST_NAME, this.DEFAULT_TEST))
},
save: function() {
if(this.isBusy || this.isTesting) {
return;
}
this.readSource();
var data = [],
i;
for(i = 0; i < this.tests.length; i++) {
data.push({
name: this.tests[i].name,
source: this.tests[i].source
});
}
this.busy(true);
nf.api.put('settings/testScripts', data, function() {
this.owner.busy(false)
}).owner = this;
},
addTest: function(test) {
this.tests.push(test);
this.showTest(this.tests.length - 1);
},
showTest: function(test) {
if(this.activeTestIndex >= 0) {
this.readSource();
}
if(typeof test == 'number') {
test = this.tests[test];
}
this.activeTestIndex = test.index();
this.testList.activateItem(test.listItem);
with(nf('#source')) {
value = test.source;
focus();
}
},
run: function() {
if(this.isBusy || this.isTesting) {
return;
}
this.isTesting = true;
this.readSource();
var test = this.activeTest();
if(!test) {
return;
}
this.results.list.clear();
test.run();
},
readSource: function() {
var test = this.activeTest();
if(test) {
test.source = nf('#source').value;
}
},
// results view
results: {
lastResult: null,
list: null,
scroll: function() {
var div = nf('#results');
div.scrollTop = div.scrollHeight;
},
newResultView: function() {
var ret = this.lastResult = new nf.Testing.ResultView(this.list.addItem());
this.scroll();
return ret;
},
addStartTime: function() {
var result = this.newResultView();
result.setClass('time');
result.setHeading('Begin at ' + nf.timeString());
},
addEndTime: function(success) {
var result = this.newResultView();
result.setClass('time');
result.setHeading((success ? 'Complete at ' : 'Fail at ') + nf.timeString());
},
sendFile: function(type, name, size) {
var result = this.newResultView();
result.setClass('sendFile');
result.setHeading('Send ' + type + ' ', name, ' (', size, ' bytes)');
},
addAction: function(action) {
var result = this.newResultView(),
i, c = nf.Testing.Test.COMMANDS, commandName, ext;
for(i in c) {
if(c[i] == action.command) {
result.setClass(commandName = i.toLowerCase());
}
}
if(action.command == c.UPDATE || action.command == c.DELETE || action.command == c.REPLICATE) {
result.setHeading(
nf.ucfirst(commandName) + ' ' + action.type + ' ',
action.id()
);
}
else if(action.command == c.ADD || action.command == c.REMOVE) {
result.setHeading(
nf.ucfirst(commandName) + ' ' + action.types[0] + ' ',
action.ids[0],
(action.command == c.ADD ? ' to ' : ' from ') + action.types[1] + ' ',
action.ids[1]
);
}
else if(action.command == c.WAIT) {
result.setHeading(
'Wait ',
Math.max(0, Math.round(parseFloat(action.duration) * 10) / 10),
action.duration == 1 ? ' second' : ' seconds'
);
}
else if(action.command == c.COMMIT) {
if('timeout' in action) {
result.setHeading('Commit ', i = action.test.commitSize(), (i == 1 ? ' change' : ' changes') + ' (', action.timeout, ' sec max)');
}
else {
result.setHeading('Commit ', i = action.test.commitSize(), i == 1 ? ' change' : ' changes');
}
if(!i) {
result.setStatus('Nothing to commit');
}
}
else if(action.command == c.PURGE) {
if('timeout' in action) {
result.setHeading('Purge ', action.table, ' (', action.timeout, ' sec max)');
}
else {
result.setHeading('Purge ', action.table);
}
}
else if(action.command == c.ORDERS) {
result.setHeading('Fetch Orders (', action.timeout, ' sec max)');
}
else if(action.command == c.LOCALE) {
result.setHeading('Use ', action.locale, ' as default locale');
}
if(action.command == c.UPDATE) {
if(fileTables.indexOf(action.table) != -1) {
// file records
result.setClass('file');
if(ext = action.id().match(/\.(jpe?g|gif|png|bmp)$/i)) {
result.setImage('data:image/' + ext[1] + ';base64,' + action.base64);
}
result.setChecksum(action.fields.checksum);
result.setFileSize(action.binary.length);
} else {
// regular records
var fields = {};
for(i in action.fields) {
if(i != action.def.primaryKey[0]) {
fields[i] = action.fields[i];
}
}
result.setDetails(fields);
}
}
return result;
},
addData: function(title, data, excludeTime) {
this.lastResult.addData(title, data, excludeTime);
this.scroll();
},
showWaitTime: function(seconds) {
seconds = Math.max(0, Math.round(parseFloat(seconds) * 10) / 10);
if(!seconds) {
this.lastResult.setClass('done');
this.lastResult.setStatus('Done')
} else {
this.lastResult.setStatus('', seconds.toFixed(1), ' seconds left');
}
this.scroll();
},
showElapsed: function(seconds, complete) {
seconds = Math.max(0, Math.round(parseFloat(seconds) * 10) / 10);
if(!complete) {
this.lastResult.setStatus('', seconds.toFixed(1), ' seconds elapsed');
}
else {
this.lastResult.setStatus('Complete in ', seconds.toFixed(1), ' seconds');
this.lastResult.setClass('done');
}
},
showChangeCount: function(portion, total) {
this.lastResult.setInfo(
'Processed ',
portion,
' of ',
total,
' change' + (total == 1 ? '' : 's')
);
this.scroll();
},
showCompleteStatus: function(complete) {
this.lastResult.setInfo(complete ? 'All records deleted' : 'Some records remain');
this.scroll();
},
showParseException: function(e) {
var result = this.newResultView();
result.setClass('parse error');
result.setHeading('Parse error on line ', e.lineNumber + 1);
result.setStatus(e.description);
result.setErrorData(e.line);
this.scroll();
},
showUserError: function(data) {
var result = this.newResultView();
result.setClass('user error');
result.setHeading('Server returned user error code ', data.errorCode);
result.setErrorData(data);
this.scroll();
},
showServerError: function(code, data) {
var result = this.newResultView();
result.setClass('server error');
result.setHeading('Server responded with code ', code);
result.setErrorData(data);
this.scroll();
},
showError: function(heading, info) {
var result = this.newResultView();
result.setClass('error');
result.setHeading(heading);
result.setStatus(info);
this.scroll();
},
confirmOrder: function(order) {
var result = this.newResultView();
result.setClass('confirmOrder');
result.setHeading('Confirm Order ', order.fields.orderId);
this.addData('Order Data', order, true);
}
},
// result view controller
ResultView: function(rootElement) {
this.rootElement = rootElement;
},
// test model class
Test: function(name, source) {
var that = this;
this.source = source;
this.name = name;
this.actions = [];
this.actionIndex = -1;
this.listItem = nf.Testing.testList.addItem();
nf(
'<',
nf(
'<a>',
this.listItem,
{
href: 'javascript:',
onclick: function() {
if(!(nf.Testing.isBusy || nf.Testing.isTesting)) {
nf.Testing.showTest(that)
}
}
}
),
name
);
this.listItem.firstChild.ondblclick = function() {
nf.Testing.rename()
};
},
ImportExport: {
importing: null,
im: function() {
nf('#importExport').className = 'import';
nf('#series').value = '';
this.show(true);
this.importing = true;
},
ex: function() {
nf('#importExport').className = 'export';
var i, x = '', t = nf.Testing.tests;
for(i = 0; i < t.length; i++) {
x += "- " + t[i].name + "\n\n" + t[i].source + "\n\n";
}
nf('#series').value = x;
this.show(true);
this.importing = false;
},
show: function(show) {
nf.Testing.busy(show);
nf('#importExport').style.display = show ? 'block' : 'none';
if(show) {
with(nf('#series')) {
focus();
select();
}
}
},
ok: function() {
if(this.importing) {
var t = nf('#series').value.trim().split(/\s*(?:[\r\n]+|^)-\s*/),
i, m;
if(!t || t.length <= 1) {
return alert('The input you entered appears to be invalid.');
}
nf.Testing.tests = [];
nf.Testing.testList.clear();
nf.Testing.activeTestIndex = -1;
for(i = 1; i < t.length; i++) {
m = t[i].match(/^(.*?)[\r\n]+([\s\S]*)$/);
if(m) {
nf.Testing.addTest(new nf.Testing.Test(m[1].trim(), m[2].trim()));
}
}
nf.Testing.showTest(0);
}
this.show(false);
},
cancel: function() {
this.show(false);
}
},
icon: 'R0lGODlhEAAQALMAAON4ce2Siu2wrtg4ONdJRfXOzeeEe+BtZ9hbVNdSTNomLNxkXdkuMtc/Puqf' +
'nv///yH5BAAAAAAALAAAAAAQABAAAASQMMhgDADnLISSl9WVbZ2XEJV1LYKQLB5xqkfwGK1xEzyB' +
'HYaDo4QYLngNzeHhMJ18AkSjwUEYirVggLCYUhOthMFRKAgODYBhMPC0ZOby9MJ+Ak6OS2CKYDNk' +
'CA9bXmwHAgQMfwRpCQ4EbA1bCYmKU0tCAgsPB5QKXg0Djw4ODA0MCqieoGx+lKeprK2JqbQRADs='
};
nf.Testing.ResultView.prototype = {
setClass: function(c) {
nf.className(this.rootElement, c, 1);
},
inClass: function(c) {
return nf.className(this.rootElement, c);
},
addData: function(title, data, excludeTime, expanded) {
var div = nf('<div>', null, {className: 'data ' + title.toLowerCase().replace(/\s+([a-z])/, function(a, b) {
return b.toUpperCase()
})}),
formatted = nf('<div>', div, {className: 'formatted'});
this.addExpander(title, div, false, expanded);
if(!excludeTime) {
this.addTime();
}
this.rootElement.appendChild(div);
this.showDataIn(data, formatted);
if(typeof data == 'object') {
var raw = nf('<div>', div, {className: 'raw'});
this.addExpander('Raw view', raw, div);
div.appendChild(raw);
nf('<', raw, nf.json_encode(data));
}
},
showDataIn: function(data, target) {
var ol, i, tbody, tr;
if(data === null) {
nf.className(target, 'null', 1);
target.innerHTML = 'NULL';
}
else if(data === true || data === false) {
nf.className(target, 'boolean', 1);
target.innerHTML = data ? 'True' : 'False';
}
else if(typeof data === 'number' || typeof data === 'string') {
nf.className(target, typeof data, 1);
target.innerHTML = '';
nf('<', target, data.toString());
}
else if(data instanceof Array) {
nf.className(target, 'array', 1);
ol = nf('<ol>', target);
ol.start = 0;
ol.style.counterReset = 'item 0';
for(i = 0; i < data.length; i++) {
arguments.callee(data[i], nf('<li>', ol));
}
}
else {
nf.className(target, 'object', 1);
tbody = nf('<tbody>', nf('<table>', target, {cellSpacing: 0}));
for(i in data) {
tr = nf('<tr>', tbody);
nf('<', nf('<th>', tr), nf.Testing.ResultView.prototype.hyphenate(i));
arguments.callee(data[i], nf('<td>', tr));
}
}
},
hyphenate: function(str) {
return str
.replace(/([^A-Z])([A-Z])/g, '$1 $2')
.replace(/&/g, ' & ')
.replace(/^([a-z])/, function(a) {
return a.toUpperCase()
});
},
addTime: function() {
nf('<', nf('<span>', this.rootElement.lastChild, {className: 'time'}), ' at ' + nf.timeString());
},
setElement: function(className, strings, nodeName) {
if(!(className in this)) {
this[className] = nf('<' + (nodeName || 'div') + '>', this.rootElement, {className: className});
}
this[className].innerHTML = '';
for(var i = 0; i < strings.length; i++) {
if(i % 2) {
nf('<', nf('<strong>', this[className]), strings[i]);
}
else {
nf('<', this[className], strings[i]);
}
}
},
setHeading: function() {
this.setElement('heading', arguments, 'h4');
},
setStatus: function() {
this.setElement('status', arguments);
},
setInfo: function() {
this.setElement('info', arguments);
this.setClass('hasInfo');
},
setErrorData: function(data) {
if(typeof data == 'string') {
this.setElement('errorData', arguments, 'pre');
}
else {
this.addData('Details', data, true, true);
}
},
setImage: function(src) {
var pa = this.getPreviewArea(),
img;
pa.innerHTML = '';
img = nf('<img>', pa, {src: src});
if(img.height > 130) {
img.height = 130;
}
if(img.width > 160) {
delete img['height'];
img.width = 160;
}
},
setFileSize: function(bytes) {
if(!('fileSize' in this)) {
this.fileSize = nf('<div>', this.getPreviewArea(), {className: 'fileSize'});
}
this.fileSize.innerHTML = bytes + ' bytes';
},
setChecksum: function(md5) {
if(!('checksum' in this)) {
this.checksum = nf('<pre>', this.getPreviewArea(), {className: 'checksum'});
}
this.checksum.innerHTML = '';
nf('<', this.checksum, this.splitChecksum(md5.substr(0, 11)) + "\n" + this.splitChecksum(md5.substr(11)));
},
splitChecksum: function(half) {
return half.substr(0, 3) + ' ' +
half.substr(3, 3) + ' ' +
half.substr(6, 3) + ' ' +
half.substr(9);
},
getPreviewArea: function() {
if(!('previewArea' in this)) {
this.previewArea = nf('<div>', null, {className: 'preview'});
this.addExpander('Preview', this.previewArea);
this.rootElement.appendChild(this.previewArea);
}
return this.previewArea;
},
setDetails: function(data) {
if(!('detailsTable' in this)) {
this.detailsTable = nf('<table>');
nf.className(this.addExpander('Details', this.detailsTable), 'details', 1);
this.detailsTable.cellSpacing = 0;
this.detailsTable.className = 'details';
nf('<tbody>', this.detailsTable);
this.rootElement.appendChild(this.detailsTable);
}
var tbody = this.detailsTable.tBodies[0],
i, j, tr = null, c = false;
while(tbody.firstChild) {
tbody.removeChild(tbody.firstChild);
}
for(i in data) {
if(typeof data[i] == 'object') {
for(j in data[i]) {
tr = this.addDetailToTBody(i + '.' + j, data[i][j], tbody, c ? '' : 'first');
c = true;
}
}
else {
tr = this.addDetailToTBody(i, data[i], tbody, c ? '' : 'first');
}
c = true;
}
if(tr) {
nf.className('tr', 'last', 1);
}
},
addDetailToTBody: function(field, value, tbody, className) {
var tr = nf('<tr>', tbody, {className: className}),
c = {className: value.match(/^-?(\d+(\.\d*)|\.\d+)$/) ? 'number' : 'string'};
nf('<', nf('<th>', tbody, c), field);
nf('<', nf('<td>', tbody, c), c.className == 'number' ? parseFloat(value).toString() : value);
return tr;
},
addExpander: function(text, target, parent, expanded) {
var div = nf('<div>', parent || this.rootElement, {className: 'expander'}),
ret = nf('<a>', div);
nf('<', ret, text);
ret.href = 'javascript:';
ret.className = expanded ? 'open' : 'closed';
if(!expanded) {
target.style.display = 'none';
}
ret.eTarget = target;
ret.onclick = function() {
var open = nf.className(this, 'open');
nf.className(this, 'open', open ? -1 : 1);
nf.className(this, 'closed', open ? 1 : -1);
this.eTarget.style.display = open ? 'none' : '';
};
}
};
// test model class nf.Testing.Test.prototype = {
index: function() {
var i = nf.Testing.tests.length;
while(i--) {
if(nf.Testing.tests[i] === this) {
return i;
}
}
},
run: function() {
var e;
if(this.actionIndex != -1) // already running
{
return;
}
nf.Testing.results.addStartTime();
try {
this.parse();
} catch(e) {
if(e instanceof this.ParseException) {
nf.Testing.results.showParseException(e);
return this.terminate(false);
} else {
throw(e);
}
}
this.commit = {
records: {},
relations: {}
};
this.runNextAction();
},
commitSize: function() {
if(!('commit' in this)) {
return 0;
}
var ret = 0, i, j;
for(i in this.commit) {
for(j in this.commit[i]) {
ret += this.commit[i][j].length;
}
}
return ret;
},
terminate: function(success) {
if('commit' in this) {
delete this['commit'];
}
this.actionIndex = -1;
nf.Testing.results.addEndTime(success);
nf.Testing.isTesting = false;
},
runNextAction: function(doNotIncrement) {
if(!doNotIncrement) {
this.actionIndex++;
}
if(this.actionIndex >= this.actions.length) {
return this.terminate(true);
}
var action = this.actions[this.actionIndex],
commands = nf.Testing.Test.COMMANDS,
i, group;
nf.Testing.results.addAction(action);
switch(action.command) {
case commands.UPDATE:
case commands.DELETE:
if(!(action.type in this.commit.records)) {
this.commit.records[action.type] = [];
}
group = this.commit.records[action.type];
i = group.length;
while(i--) {
if(group[i][action.def.primaryKey[0]] == action.id()) {
group.splice(i, 1);
break;
}
}
group.push(action.fields);
break;
case commands.ADD:
case commands.REMOVE:
if(action.types[0] > action.types[1]) {
action.types = [action.types[1], action.types[0]];
action.ids = [action.ids[1], action.ids[0]];
}
group = encodeURIComponent(action.types[0])
+ '&'
+ encodeURIComponent(action.types[1]);
if(!(group in this.commit.relations)) {
this.commit.relations[group] = [];
}
group = this.commit.relations[group];
i = group.length;
while(i--) {
if(group[i].a == action.ids[0] && group[i].b == action.ids[1]) {
group.splice(i, 1);
return;
}
}
group.push({
a: action.ids[0],
b: action.ids[1],
x: action.command == commands.ADD
});
break;
case commands.WAIT:
this.startTimer(action.duration);
this.ontimeout = this.runNextAction;
return;
case commands.COMMIT:
if(this.commitSize()) {
this.setApiTimeout(action);
this.startTimer();
nf.Testing.results.addData('Request', this.commit);
this.lastCall = nf.api.put(
'commit', this.commit,
this.commitResponse
).set('owner', this);
return;
}
break;
case commands.REPLICATE:
this.lastCall = nf.api.post(
'records/' + action.type + '/external',
action.id(),
this.replicateResponse
).set('owner', this);
nf.Testing.results.addData('Request', this.lastCall.body);
return;
case commands.PURGE:
this.setApiTimeout(action);
this.startTimer();
this.lastCall = nf.api.del(
"records/" + action.type,
this.purgeResponse
).set('owner', this);
nf.Testing.results.addData('Request', this.lastCall.body);
return;
case commands.ORDERS:
this.setApiTimeout(action);
this.startTimer();
this.lastCall = nf.api.get(
'newOrders',
this.ordersResponse
).set('owner', this);
nf.Testing.results.addData('Request', this.lastCall.body);
return;
}
this.runNextAction();
},
replicateResponse: function(r) {
if(!this.success) {
return this.owner.showErrorResponse(r);
}
nf.Testing.results.addData('Response', r);
this.owner.runNextAction();
},
setApiTimeout: function(action) {
var secs = (action && ('timeout' in action)) ? action.timeout : null;
if(typeof secs == 'number') {
nf.api.headers['X-Timeout'] = secs;
}
else if('X-Timeout' in nf.api.headers) {
delete nf.api.headers['X-Timeout'];
}
},
showErrorResponse: function(r) {
var code = this.lastCall.xh.status;
if(code >= 300) {
nf.Testing.results.addData('Error', 'Status code ' + code + ' (see report below)');
nf.Testing.results.showServerError(code, r);
} else {
nf.Testing.results.addData('Error', 'User error code ' + r.errorCode + ' (see report below)');
nf.Testing.results.showUserError(r);
}
return this.terminate(false);
},
purgeResponse: function(r) {
this.owner.stopTimer();
if(!this.success) {
return this.owner.showErrorResponse(r);
}
nf.Testing.results.showCompleteStatus(!r.incomplete);
nf.Testing.results.addData('Response', r);
this.owner.runNextAction(r.incomplete);
},
ordersResponse: function(r) {
this.owner.stopTimer();
if(!this.success) {
return this.owner.showErrorResponse(r);
}
nf.Testing.results.addData('Response', r);
this.owner.confirmOrders(r.orders);
},
confirmOrders: function(orders) {
if(!orders.length) {
return this.runNextAction();
}
var order = orders.shift();
nf.Testing.results.confirmOrder(order);
this.startTimer();
this.setApiTimeout();
this.lastCall = nf.api.put(
'confirmOrder?id=' + encodeURIComponent(order.fields.orderId),
null,
this.confirmOrderResponse
).set('owner', this).set('orders', orders);
nf.Testing.results.addData('Request', this.lastCall.body);
},
confirmOrderResponse: function(r) {
this.owner.stopTimer();
if(!this.success) {
return this.owner.showErrorResponse(r);
}
nf.Testing.results.addData('Response', r);
this.owner.confirmOrders(this.orders);
},
commitResponse: function(r) {
this.owner.stopTimer();
if(!this.success) {
return this.owner.showErrorResponse(r);
}
nf.Testing.results.addData('Response', r);
var type, i, j, commitSize = this.owner.commitSize();
if('records' in r.complete) {
for(type in r.complete.records) {
for(i in r.complete.records[type]) {
for(j = 0; j < this.owner.commit.records[type].length; j++) {
if(this.owner.commit.records[type][j][nf.Testing.schema[nf.Testing.singular[type]].primaryKey[0]] == i) {
this.owner.commit.records[type].splice(j, 1);
}
}
}
}
}
if('relations' in r.complete) {
for(type in r.complete.relations) {
for(i = 0; i < r.complete.relations[type].length; i++) {
for(j = 0; j < this.owner.commit.relations[type].length; j++) {
if(r.complete.relations[type][i].a === this.owner.commit.relations[type][j].a &&
r.complete.relations[type][i].b === this.owner.commit.relations[type][j].b) {
this.owner.commit.relations[type].splice(j, 1);
}
}
}
}
}
if(commitSize == this.owner.commitSize()) {
nf.Testing.results.showError('Fatal error', 'No changes were processed. Try increasing timeout.');
return this.owner.terminate(false);
}
nf.Testing.results.showChangeCount(commitSize - this.owner.commitSize(), commitSize);
this.owner.filesToSend = r.filesToSend;
this.owner.sendFiles();
},
sendFiles: function() {
var type, id, i;
for(type in this.filesToSend) {
if(this.filesToSend[type].length) {
id = this.filesToSend[type].shift();
i = this.actions.length;
while(i--) {
if(this.actions[i].command == nf.Testing.Test.COMMANDS.UPDATE &&
this.actions[i].type == type &&
this.actions[i].id() == id) {
break;
}
}
nf.api.headers['Content-Type'] = 'application/octet-stream';
nf.api.post('files/' + type + '/' + id, this.actions[i].binary, this.fileSent).owner = this;
delete nf.api.headers['Content-Type'];
nf.Testing.results.sendFile(type, id, this.actions[i].binary.length);
this.startTimer();
return;
}
}
this.runNextAction(this.commitSize());
},
fileSent: function(r) {
this.owner.stopTimer();
this.owner.sendFiles();
},
startTimer: function(from) {
var that = this;
this.countDownFrom = from || null;
this.startTime = new Date();
this.updateTimer();
this.timerInterval = setInterval(function() {
that.updateTimer()
}, 100);
},
stopTimer: function() {
clearInterval(this.timerInterval);
if(this.countDownFrom) {
nf.Testing.results.showWaitTime(0);
}
else {
nf.Testing.results.showElapsed(this.elapsed(), true);
}
},
elapsed: function() {
return ((new Date()).getTime() - this.startTime.getTime()) / 1000;
},
updateTimer: function() {
if(this.countDownFrom) {
if(this.elapsed() >= this.countDownFrom) {
this.stopTimer();
this.runNextAction();
} else {
nf.Testing.results.showWaitTime(this.countDownFrom - this.elapsed());
}
}
else {
nf.Testing.results.showElapsed(this.elapsed());
}
},
parse: function() {
var lines = this.source.trim().split(/\s*[\r\n]+\s*/),
i, line, command, commandId, action, attr, parts, j, k,
lastAction = null, binary = false, localize,
commands = nf.Testing.Test.COMMANDS;
this.actions = [];
this.locale = nf.Testing.locale;
for(i = 0; i < lines.length; i++) {
line = lines[i];
// blanks
if(line == '') {
throw new this.ParseException(i, line, "The test source is empty.");
}
// comments
if(line.substr(0, 1) == '#') {
continue;
}
// attributes
if(!binary && (attr = line.match(/^(\w+)(?:\.(\w+))?\s*=\s*(.*)$/))) {
if(lastAction === null) {
throw new this.ParseException(i, line, "Tried to assign property without and UPDATE command.");
}
localize = false;
if(lastAction.def.localize.indexOf(attr[1]) != -1) {
localize = true;
}
else if(attr[1] in lastAction.def.columns) {
if(attr[2]) {
throw new this.ParseException(i, line,
"Cannot localize field " + lastTable.type + "." + attr[1]);
}
} else {
if(!nf.Testing.allowCustomFields) {
throw new this.ParseException(i, line,
"Field '" + attr[1] + "' is not in " + lastAction.table +
" schema and the server does not allow custom fields.");
}
localize = true;
}
if(localize) {
if(!(attr[1] in lastAction.fields)) {
lastAction.fields[attr[1]] = {};
}
lastAction.fields[attr[1]][attr[2] || this.locale] = attr[3];
} else {
lastAction.fields[attr[1]] = attr[3];
}
continue;
} else if(binary === null) {
binary = true;
}
// binary (base64)
if(binary) {
if(line.match(/;$/)) {
line = line.replace(';', '');
binary = false;
}
lastAction.base64 += line;
if(!binary) {
if(!nf.base64.validate(lastAction.base64)) {
throw new this.ParseException(i, lastAction.base64, "Invalid base64 blob.");
}
lastAction.binary = nf.base64.decode(lastAction.base64);
lastAction.fields.checksum = nf.base64.encode(nf.md5(lastAction.binary, true)).substr(0, 22);
}
continue;
}
// commands
command = line.match(/^\S*/)[0].toUpperCase();
if(command in commands) {
commandId = commands[command];
action = new nf.Testing.Test.Action(commandId);
action.test = this;
parts = line.match(arguments.callee.patterns[commandId]);
if(!parts) {
throw new this.ParseException(i, line, "Invalid " + command + " command.");
}
switch(commandId) {
case commands.UPDATE:
case commands.DELETE:
case commands.REPLICATE:
action.type = null;
for(j in nf.Testing.singular) {
if(j.toLowerCase() == parts[1].toLowerCase()) {
action.type = j;
}
}
if(action.type === null) {
throw new this.ParseException(i, line, "Unknown type '" + parts[1] + "'.");
}
action.table = nf.Testing.singular[action.type];
action.def = nf.Testing.schema[action.table];
// Work-around for server's dependence on the @active flag
// action.fields = {'@active': commandId === commands.DELETE ? '0' : '1'};
action.fields = {};
action.fields[action.def.primaryKey[0]] = parts[2];
if(commandId == commands.DELETE) {
action.fields[DELETE_KEY] = true;
}
else if(commandId == commands.UPDATE) {
lastAction = action;
if(fileTables.indexOf(action.table) != -1) {
action.base64 = '';
binary = null;
}
}
break;
case commands.PURGE:
action.table = null;
for(j in nf.Testing.schema) {
if(j.toLowerCase() == parts[1].toLowerCase()) {
action.table = j;
}
}
if(action.table === null) {
throw new this.ParseException(i, line, "Unknown table '" + parts[1] + "'.");
}
action.type = nf.Testing.schema[action.table].singular;
if(typeof parts[2] == 'string') {
action.timeout = parseInt(parts[2]);
}
break;
case commands.ADD:
case commands.REMOVE:
action.types = [null, null];
var types = [parts[1], parts[3]];
for(k = 0; k < 2; k++) {
for(j in nf.Testing.singular) {
if(j.toLowerCase() == types[k].toLowerCase()) {
action.types[k] = j;
}
}
if(action.types[k] === null) {
throw new this.ParseException(i, line, "Unknown type '" + types[k] + "'.");
}
}
action.ids = [parts[2], parts[4]];
break;
case commands.ORDERS:
action.timeout = nf.Testing.DEFAULT_ORDER_TIMEOUT;
case commands.COMMIT:
if(typeof parts[1] == 'string') {
action.timeout = parseInt(parts[1]);
}
break;
case commands.WAIT:
action.duration = parseFloat(parts[1]);
break;
case commands.LOCALE:
action.locale = this.locale = parts[1];
break;
}
this.actions.push(action);
} else {
throw new this.ParseException(i, line, "Unknown command.");
}
}
},
// parse exception
ParseException: function(i, line, description) {
this.lineNumber = i;
this.line = line;
this.description = description;
}
};
nf.Testing.Test.prototype.ParseException.prototype = {
toString: function() {
return "Line " + this.lineNumber + " : " + this.line + "\n" + this.description;
}
};
// test action model class nf.Testing.Test.Action = function(command) {
if(typeof command == 'number') {
return this.command = command;
}
var c = nf.Testing.Test.COMMANDS,
i;
for(i in c) {
if(i == command.toUpperCase()) {
this.command = c[i];
}
}
};
nf.Testing.Test.Action.prototype = {
id: function() {
if('fields' in this) {
return this.fields[this.def.primaryKey[0]];
}
}
};
nf.Testing.Test.COMMANDS = {
UPDATE: 1, DELETE: 2, PURGE: 3, ADD: 4, REMOVE: 5, COMMIT: 6, WAIT: 7, ORDERS: 8, REPLICATE: 9, LOCALE: 10
};
nf.Testing.Test.prototype.parse.patterns = {}; with(nf.Testing.Test.prototype.parse) {
patterns[nf.Testing.Test.COMMANDS.UPDATE] = /^UPDATE\s+(\w+)\s+(\S.*)$/i; patterns[nf.Testing.Test.COMMANDS.DELETE] = /^DELETE\s+(\w+)\s+(\S.*)$/i; patterns[nf.Testing.Test.COMMANDS.REPLICATE] = /^REPLICATE\s+(\w+)\s+(\S.*)$/i; patterns[nf.Testing.Test.COMMANDS.PURGE] = /^PURGE\s+(\w+)(?:\s+(\d+))?$/i; patterns[nf.Testing.Test.COMMANDS.ADD] = /^ADD\s+(\w+)\s+(.+?)\s+TO\s+(\w+)\s+(.+)$/i; patterns[nf.Testing.Test.COMMANDS.REMOVE] = /^REMOVE\s+(\w+)\s+(.+?)\s+FROM\s+(\w+)\s+(.+)$/i; patterns[nf.Testing.Test.COMMANDS.COMMIT] = /^COMMIT(?:\s+(\d+))?$/i; patterns[nf.Testing.Test.COMMANDS.WAIT] = /^WAIT\s+(\d+(?:\.\d*)?|\.\d+)$/i; patterns[nf.Testing.Test.COMMANDS.ORDERS] = /^ORDERS(?:\s+(\d+))?$/i; patterns[nf.Testing.Test.COMMANDS.LOCALE] = /^LOCALE\s+(\w+)\s*$/i;
}