Commit 69a541d3 authored by Caleb James DeLisle's avatar Caleb James DeLisle Committed by Tristan Cavelier

Rewrote XWiki backed storage to work with new API.

Stores documents and attachments, attachments accept and return Blob type.
Tests are run with mocks and if the URL containt /xwiki/bin/ then they are run on the live wiki
    (user must be logged in for them to pass).
parent f769b55a
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, vars: true */
/*global toSend: true, jIO: true, jQuery: true, btoa: true */ /*global
jIO: true,
$: true,
XMLHttpRequest: true,
Blob: true,
FormData: true,
window: true
*/
/**
* JIO XWiki Storage. Type = 'xwiki'.
* XWiki Document/Attachment storage.
*/
jIO.addStorageType('xwiki', function (spec, my) {
spec = spec || {};
var that, priv, xwikistorage;
that = my.basicStorage(spec, my);
priv = {};
/** /**
* JIO XWiki based storage. Type = 'xwiki'. * Get the Space and Page components of a documkent ID.
* Edits XWiki documents as html using html editor.
* Test this code using the following inputs:
* *
{"type":"xwiki","username":"Admin","password":"admin","xwikiurl":"http://127.0.0 * @param id the document id.
.1:8080/xwiki","space":"OfficeJS"} * @return a map of { 'space':<Space>, 'page':<Page> }
*/ */
var getParts = function (id) {
(function ($) { if (id.indexOf('/') === -1) {
return {
var newXWikiStorage = function (spec, my) { space: 'Main',
var that, priv, escapeDocId, restoreDocId, page: id
doWithFormToken, getDates, super_serialized; };
}
/** The input configuration. */ return {
spec = spec || {}; space: id.substring(0, id.indexOf('/')),
page: id.substring(id.indexOf('/') + 1)
/** The "public" object which will have methods called on it. */
that = my.basicStorage(spec, my);
/** "private" fields. */
priv = {
username: spec.username || '',
password: spec.password || '',
xwikiurl: spec.xwikiurl || '',
space: spec.space || ''
}; };
};
//--------------------- Private Functions ---------------------// /**
/** Escape a document ID by URL escaping all '/' characters. */ * Get the Anti-CSRF token and do something with it.
escapeDocId = function (docId) { *
// jslint: replaced "." with [\w\W] * @param andThen function which is called with (formToken, err)
return docId.replace(/[\w\W]html$/, '').split('/').join('%2F'); * as parameters.
}; */
var doWithFormToken = function (andThen) {
$.ajax({
url: priv.formTokenPath,
type: "GET",
async: true,
dataType: 'text',
success: function (html) {
// this is unreliable
//var token = $('meta[name=form_token]', html).attr("content");
var m = html.match(/<meta name="form_token" content="(\w*)"\/>/);
var token = (m && m[1]) || null;
if (!token) {
andThen(null, {
"status": 404,
"statusText": "Not Found",
"error": "err_form_token_not_found",
"message": "Anti-CSRF form token was not found in page",
"reason": "XWiki main page did not contain expected " +
"Anti-CSRF form token"
});
} else {
andThen(token, null);
}
},
error: function (jqxhr, err, cause) {
andThen(null, {
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Could not get Anti-CSRF form token from [" +
priv.xwikiurl + "]",
"reason": cause
});
},
});
};
/** Restore a document id from the escaped form. */ /**
restoreDocId = function (escapedDocId) { * Get the REST read URL for a document.
return escapedDocId.split('%2F').join('/') + '.html'; *
}; * @param docId the id of the document.
* @return the REST URL for accessing this document.
*/
var getDocRestURL = function (docId) {
var parts = getParts(docId);
return priv.xwikiurl + '/rest/wikis/'
+ priv.wiki + '/spaces/' + parts.space + '/pages/' + parts.page;
};
/*
* Wrapper for the xwikistorage based on localstorage JiO store.
*/
xwikistorage = {
/** /**
* Get the Anti-CSRF token and do something with it. * Get content of an XWikiDocument.
* *
* @param docId document id of document which you have permission to edit. * @param docId the document ID.
* @param whatToDo function which is called with form token as parameter. * @param andThen a callback taking (doc, err), doc being the document
* json object and err being the error if any.
*/ */
doWithFormToken = function (docId, whatToDo) { getItem: function (docId, andThen) {
var url = priv.xwikiurl + '/bin/edit/' + priv.space + '/' +
escapeDocId(docId) + '?editor=wiki&cachebuster=' + Date.now();
$.ajax({ $.ajax({
url: url, url: getDocRestURL(docId),
type: "GET", type: "GET",
async: true, async: true,
dataType: 'text', dataType: 'xml',
headers: { success: function (xmlData) {
'Authorization': 'Basic ' + btoa(priv.username + ':' + var out = {};
priv.password) var xd = $(xmlData);
xd.find('modified').each(function () {
out._last_modified = Date.parse($(this).text());
});
xd.find('created').each(function () {
out._creation_date = Date.parse($(this).text());
});
xd.find('title').each(function () { out.title = $(this).text(); });
xd.find('parent').each(function () { out.parent = $(this).text(); });
xd.find('syntax').each(function () { out.syntax = $(this).text(); });
xd.find('content').each(function () {
out.content = $(this).text();
});
out._id = docId;
andThen(out, null);
}, },
success: function (html) { error: function (jqxhr, err, cause) {
whatToDo($(html).find('input[name=form_token]').attr('value')); if (jqxhr.status === 404) {
andThen(null, null);
return;
}
andThen(null, {
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Failed to get document [" + docId + "]",
"reason": cause
});
} }
}); });
}; },
/** /**
* Get the creation and modification dates for a page. * Get content of an XWikiAttachment.
* *
* @param docId the ID of the document. * @param attachId the attachment ID.
* @param callWhenDone callback, will be called when function finishes. * @param andThen a callback taking (attach, err), attach being the
* attachment blob and err being the error if any.
*/ */
getDates = function (docId, callWhenDone) { getAttachment: function (docId, fileName, andThen) {
// http://127.0.0.1:8080/xwiki/rest/wikis/xwiki/ // need to do this manually, jquery doesn't support returning blobs.
// spaces/Main/pages/<pageName> var xhr = new XMLHttpRequest();
var map = {}; var parts = getParts(docId);
$.ajax({ var url = priv.xwikiurl + '/bin/download/' + parts.space +
url: priv.xwikiurl + '/rest/wikis/' + 'xwiki' + '/spaces/' + priv.space "/" + parts.page + "/" + fileName;
+ '/pages/' + escapeDocId(docId) + '?cachebuster=' + Date.now(), xhr.open('GET', url, true);
type: "GET", xhr.responseType = 'blob';
async: true,
dataType: 'xml', xhr.onload = function (e) {
headers: { if (xhr.status === 200) {
'Authorization': 'Basic ' + btoa(priv.username + ':' + var contentType = xhr.getResponseHeader("Content-Type");
priv.password) if (contentType.indexOf(';') > -1) {
}, contentType = contentType.substring(0, contentType.indexOf(';'));
success: function (xmlData) { }
$(xmlData).find('modified').each(function () { var blob = new Blob([xhr.response], {type: contentType});
map._last_modified = Date.parse($(this).text()); andThen(blob);
}); } else {
$(xmlData).find('created').each(function () { andThen(null, {
map._creation_date = Date.parse($(this).text()); "status": xhr.status,
"statusText": xhr.statusText,
"error": "err_network_error",
"message": "Failed to get attachment ["
+ docId + "/" + fileName + "]",
"reason": "Error getting data from network"
}); });
callWhenDone();
} }
}); };
return map;
}; xhr.send();
},
//--------------------- Public Functions ---------------------//
/** Get a serialized form of the module state. */
super_serialized = that.serialized;
that.serialized = function () {
var o = super_serialized(), key;
for (key in priv) {
if (priv.hasOwnProperty(key)) {
o[key] = priv[key];
}
}
return o;
};
/** Check that the storage module is properly setup. */
that.validateState = function () {
var key;
for (key in priv) {
if (priv.hasOwnProperty(key) && !priv[key]) {
return 'Must specify "' + key + '".';
}
}
return '';
};
/** Alias to put() */
that.post = function (command) {
that.put(command);
};
/** /**
* Saves a document as an XWikiDocument. * Store an XWikiDocument.
* *
* @param command must contain document ID and document content. * @param id the document identifier.
* @param doc the document JSON object containing
* "parent", "title", "content", and/or "syntax" keys.
* @param andThen a callback taking (err), err being the error if any.
*/ */
that.put = function (command) { setItem: function (id, doc, andThen) {
doWithFormToken(command.getDocId(), function (formToken) { doWithFormToken(function (formToken, err) {
if (!formToken) { if (err) {
throw new Error("missing form token"); that.error(err);
return;
} }
var parts = getParts(id);
$.ajax({ $.ajax({
url: priv.xwikiurl + '/bin/preview/' + priv.space + '/' + url: priv.xwikiurl + "/bin/preview/" + parts.space + '/' + parts.page,
escapeDocId(command.getDocId()),
type: "POST", type: "POST",
async: true, async: true,
dataType: 'text', dataType: 'text',
headers: {
'Authorization': 'Basic ' + btoa(priv.username + ':' +
priv.password)
},
data: { data: {
parent: '', parent: doc.parent || '',
title: '', title: doc.title || '',
xredirect: '', xredirect: '',
language: 'en', language: 'en',
RequiresHTMLConversion: 'content', // RequiresHTMLConversion: 'content',
content_syntax: 'xwiki/2.1', // content_syntax: doc.syntax || 'xwiki/2.1',
content: command.getDocContent(), content: doc.content || '',
xeditaction: 'edit', xeditaction: 'edit',
comment: 'Saved by OfficeJS', comment: 'Saved by JiO',
action_saveandcontinue: 'Save & Continue', action_saveandcontinue: 'Save & Continue',
syntaxId: 'xwiki/2.1', syntaxId: doc.syntax || 'xwiki/2.1',
xhidden: 0, xhidden: 0,
minorEdit: 0, minorEdit: 0,
ajax: true, ajax: true,
form_token: formToken form_token: formToken
}, },
success: function () { success: function () {
that.success({ andThen(null);
ok: true, },
id: command.getDocId() error: function (jqxhr, err, cause) {
andThen({
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Failed to store document [" + id + "]",
"reason": cause
}); });
} }
}); });
}); });
}; // end put },
/** /**
* Loads a document from the XWiki storage. * Store an XWikiAttachment.
*
* @param docId the ID of the document to attach to.
* @param fileName the attachment file name.
* @param mimeType the MIME type of the attachment content.
* @param content the attachment content.
* @param andThen a callback taking one parameter which is the error if any.
*/ */
that.get = function (command) { setAttachment: function (docId, fileName, mimeType, content, andThen) {
// /bin/view/Main/WebHomee?xpage=plain doWithFormToken(function (formToken, err) {
/** if (err) {
* Protocol specification: that.error(err);
* { return;
* "_id": "somePage", }
* "content": "aoeu", var parts = getParts(docId);
* "_creation_date": 1348154789478, var blob = (content.constructor === "function Blob() { [native code] }")
* "_last_modified": 1348154789478 ? content : new Blob([content], {type: mimeType});
* } var fd = new FormData();
*/ fd.append("filepath", blob, fileName);
var doc, fd.append("form_token", formToken);
pendingRequests = 2, var xhr = new XMLHttpRequest();
finishedRequest = function () { xhr.open('POST', priv.xwikiurl + "/bin/upload/" +
pendingRequests -= 1; parts.space + '/' + parts.page, true);
if (pendingRequests < 1) { xhr.onload = function (e) {
that.success(doc); if (xhr.status === 302 || xhr.status === 200) {
andThen(null);
} else {
andThen({
"status": xhr.status,
"statusText": xhr.statusText,
"error": "err_network_error",
"message": "Failed to store attachment ["
+ docId + "/" + fileName + "]",
"reason": "Error posting data"
});
} }
}; };
doc = (function () { xhr.send(fd);
var resultMap = getDates(command.getDocId(), finishedRequest); });
},
removeItem: function (id, andThen) {
doWithFormToken(function (formToken, err) {
if (err) {
that.error(err);
return;
}
var parts = getParts(id);
$.ajax({ $.ajax({
url: priv.xwikiurl + '/bin/get/' + priv.space + '/' + url: priv.xwikiurl + "/bin/delete/" + parts.space + '/' + parts.page,
escapeDocId(command.getDocId()) + '?xpage=plain&cachebuster=' + type: "POST",
Date.now(),
type: "GET",
async: true, async: true,
dataType: 'text', dataType: 'text',
headers: { data: {
'Authorization': 'Basic ' + btoa(priv.username + ':' + confirm: '1',
priv.password) form_token: formToken
},
success: function () {
andThen(null);
}, },
success: function (html) { error: function (jqxhr, err, cause) {
resultMap.content = html; andThen({
finishedRequest(); "status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Failed to delete document [" + id + "]",
"reason": cause
});
} }
}); });
return resultMap; });
}()); },
doc._id = command.getDocId();
}; // end get
/** removeAttachment: function (docId, fileName, andThen) {
* Gets a document list from the xwiki storage. var parts = getParts(docId);
* It will retreive an array containing files meta data owned by doWithFormToken(function (formToken, err) {
* the user. if (err) {
* @method allDocs that.error(err);
*/ return;
that.allDocs = function (command) {
// http://127.0.0.1:8080/xwiki/rest/wikis/xwiki/spaces/Main/pages
$.ajax({
url: priv.xwikiurl + '/rest/wikis/' + 'xwiki' + '/spaces/' +
priv.space + '/pages?cachebuster=' + Date.now(),
type: "GET",
async: true,
dataType: 'xml',
headers: {
'Authorization': 'Basic ' + btoa(priv.username + ':' +
priv.password)
},
success: function (xmlData) {
/** Protocol definition:
* {
* "total_rows":2,
* "rows":[{
* "id":"b",
* "key":"b",
* "value":{
* "content":"aoeu",
* "_creation_date":1348154789478,
* "_last_modified":1348154789478
* }
* },
* {
* "id":"oeau",
* "key":"oeau",
* "value"{
* "content":"oeu",
* "_creation_date":1348154834680,
* "_last_modified":1348154834680
* }
* }
* ]
* }
*/
var totalRows = 0,
data = [],
// The number of async calls which are waiting to return.
outstandingCalls = 0,
toSend;
$(xmlData).find('name').each(function () {
outstandingCalls += 1;
var id = restoreDocId($(this).text()),
entry = {
'id': id,
'key': id,
'value': getDates(id, function () {
outstandingCalls -= 1;
if (outstandingCalls < 1) {
that.success(toSend);
}
})
};
data[totalRows += 1] = entry;
});
toSend = {
'total_rows': totalRows,
'rows': data
};
/* TODO: Include the content if requested.
if (!command.getOption('metadata_only')) {
getContent();
} else {
that.success(toSend);
}
*/
},
error: function (type) {
if (type.status === 404) {
type.message = 'Cannot find "' + command.getDocId() +
'"informations.';
type.reason = 'missing';
that.error(type);
} else {
type.reason = 'Cannot get "' + command.getDocId() +
'"informations';
type.message = type.reason + '.';
that.retry(type);
}
} }
});
};
/**
* Removes a document from the XWiki storage.
*/
that.remove = function (command) {
// http://127.0.0.1:8080/xwiki/bin/delete/Main/WebHomee?
// confirm=1&form_token= //r7x0oGBSk2EFm2fxVULfFA
doWithFormToken(command.getDocId(), function (formToken) {
$.ajax({ $.ajax({
url: priv.xwikiurl + '/bin/delete/' + priv.space + '/' + url: priv.xwikiurl + "/bin/delattachment/" + parts.space + '/' +
escapeDocId(command.getDocId()), parts.page + '/' + fileName,
type: "POST", type: "POST",
async: true, async: true,
dataType: 'text', dataType: 'text',
headers: {
'Authorization': 'Basic ' + btoa(priv.username + ':' +
priv.password)
},
data: { data: {
confirm: 1, ajax: '1',
form_token: formToken form_token: formToken
}, },
success: function () { success: function () {
that.success({ andThen(null);
ok: true, },
id: command.getDocId() error: function (jqxhr, err, cause) {
andThen({
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Failed to delete attachment ["
+ docId + '/' + fileName + "]",
"reason": cause
}); });
} }
}); });
}); });
}; // end remove }
return that; };
// ==================== Tools ====================
/**
* Update [doc] the document object and remove [doc] keys
* which are not in [new_doc]. It only changes [doc] keys not starting
* with an underscore.
* ex: doc: {key:value1,_key:value2} with
* new_doc: {key:value3,_key:value4} updates
* doc: {key:value3,_key:value2}.
* @param {object} doc The original document object.
* @param {object} new_doc The new document object
*/
priv.documentObjectUpdate = function (doc, new_doc) {
var k;
for (k in doc) {
if (doc.hasOwnProperty(k)) {
if (k[0] !== '_') {
delete doc[k];
}
}
}
for (k in new_doc) {
if (new_doc.hasOwnProperty(k)) {
if (k[0] !== '_') {
doc[k] = new_doc[k];
}
}
}
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
priv.objectIsEmpty = function (obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
};
// ==================== attributes ====================
// the wiki to store stuff in
priv.wiki = spec.wiki || 'xwiki';
// unused
priv.username = spec.username;
priv.language = spec.language;
// URL location of the wiki, unused since
// XWiki doesn't currently allow cross-domain requests.
priv.xwikiurl = spec.xwikiurl ||
window.location.href.replace(/\/xwiki\/bin\//, '/xwiki\n').split('\n')[0];
// should be: s@/xwiki/bin/.*$@/xwiki@
// but jslint gets in the way.
// Which URL to load for getting the Anti-CSRF form token, used for testing.
priv.formTokenPath = spec.formTokenPath || priv.xwikiurl;
that.specToStore = function () {
return {
"username": priv.username,
"language": priv.language,
"xwikiurl": priv.xwikiurl,
};
};
// can't fo wrong since no parameters are required.
that.validateState = function () {
return '';
};
// ==================== commands ====================
/**
* Create a document in local storage.
* @method post
* @param {object} command The JIO command
*/
that.post = function (command) {
var docId = command.getDocId();
if (!(typeof docId === "string" && docId !== "")) {
setTimeout(function () {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
});
return;
}
xwikistorage.getItem(docId, function (doc, err) {
if (err) {
that.error(err);
} else if (doc === null) {
// the document does not exist
xwikistorage.setItem(command.getDocId(),
command.cloneDoc(),
function (err) {
if (err) {
that.error(err);
} else {
that.success({
"ok": true,
"id": command.getDocId()
});
}
});
} else {
// the document already exists
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot create a new document",
"reason": "Document already exists (use 'put' to modify it)"
});
}
});
}; };
jIO.addStorageType('xwiki', newXWikiStorage);
}(jQuery)); /**
* Create or update a document in local storage.
* @method put
* @param {object} command The JIO command
*/
that.put = function (command) {
xwikistorage.getItem(command.getDocId(), function (doc, err) {
if (err) {
that.error(err);
} else if (doc === null) {
doc = command.cloneDoc();
} else {
priv.documentObjectUpdate(doc, command.cloneDoc());
}
// write
xwikistorage.setItem(command.getDocId(), doc, function (err) {
if (err) {
that.error(err);
} else {
that.success({
"ok": true,
"id": command.getDocId()
});
}
});
});
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that.putAttachment = function (command) {
xwikistorage.getItem(command.getDocId(), function (doc, err) {
if (err) {
that.error(err);
} else if (doc === null) {
// the document does not exist
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Impossible to add attachment",
"reason": "Document not found"
});
} else {
// Document exists, upload attachment.
xwikistorage.setAttachment(command.getDocId(),
command.getAttachmentId(),
command.getAttachmentMimeType(),
command.getAttachmentData(),
function (err) {
if (err) {
that.error(err);
} else {
that.success({
"ok": true,
"id": command.getDocId() + "/" + command.getAttachmentId()
});
}
});
}
});
};
/**
* Get a document or attachment
* @method get
* @param {object} command The JIO command
*/
that.get = that.getAttachment = function (command) {
if (typeof command.getAttachmentId() === "string") {
// seeking for an attachment
xwikistorage.getAttachment(command.getDocId(),
command.getAttachmentId(),
function (attach, err) {
if (err) {
that.error(err);
} else if (attach !== null) {
that.success(attach);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Attachment does not exist"
});
}
});
} else {
// seeking for a document
xwikistorage.getItem(command.getDocId(), function (doc, err) {
if (err) {
that.error(err);
} else if (doc !== null) {
that.success(doc);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
}
};
/**
* Remove a document or attachment
* @method remove
* @param {object} command The JIO command
*/
that.remove = that.removeAttachment = function (command) {
var notFoundError = function (word) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": word + " not found",
"reason": "missing"
});
};
var objId = command.getDocId();
var complete = function (err) {
if (err) {
that.error(err);
} else {
that.success({
"ok": true,
"id": objId
});
}
};
if (typeof command.getAttachmentId() === "string") {
objId += '/' + command.getAttachmentId();
xwikistorage.removeAttachment(command.getDocId(),
command.getAttachmentId(),
complete);
} else {
xwikistorage.removeItem(objId, complete);
}
};
/**
* Get all filenames belonging to a user from the document index
* @method allDocs
* @param {object} command The JIO command
*/
that.allDocs = function () {
setTimeout(function () {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Your are not allowed to use this command",
"reason": "xwikistorage forbids AllDocs command executions"
});
});
};
return that;
});
...@@ -164,7 +164,8 @@ generateTools = function (test_namespace) { ...@@ -164,7 +164,8 @@ generateTools = function (test_namespace) {
var o = {}; var o = {};
o.t = test_namespace; o.t = test_namespace;
o.clock = sinon.useFakeTimers(); o.server = o.t.sandbox.server;
o.clock = o.t.sandbox.clock;
o.clock.tick(base_tick); o.clock.tick(base_tick);
o.spy = basicSpyFunction; o.spy = basicSpyFunction;
o.tick = basicTickFunction; o.tick = basicTickFunction;
...@@ -239,10 +240,8 @@ generateTools = function (test_namespace) { ...@@ -239,10 +240,8 @@ generateTools = function (test_namespace) {
switch (type) { switch (type) {
case "dav": case "dav":
return 'https:\\/\\/ca-davstorage:8080\\/' + path + '(\\?.*|$)'; return 'https:\\/\\/ca-davstorage:8080\\/' + path + '(\\?.*|$)';
break; default:
case "s3":
return path; return path;
break;
} }
}; };
o.addFakeServerResponse = function (type, method, path, status, response) { o.addFakeServerResponse = function (type, method, path, status, response) {
...@@ -6281,6 +6280,442 @@ test ('Get revision List', function () { ...@@ -6281,6 +6280,442 @@ test ('Get revision List', function () {
o.jio.stop(); o.jio.stop();
}); });
*/ */
;(function() {
// These tests will only run if we are running the suite inside of XWiki.
module ('Jio XWikiStorage');
var setUp = function(that, liveTest) {
var o = generateTools(that);
o.server = sinon.fakeServer.create();
o.jio = JIO.newJio({type:'xwiki',formTokenPath:'form_token'});
o.addFakeServerResponse("xwiki", "GET", "form_token", 200,
'<meta name="form_token" content="OMGHAX"/>');
o._addFakeServerResponse = o.addFakeServerResponse;
o.expectedRequests = [];
o.addFakeServerResponse = function(a,b,c,d,e) {
o._addFakeServerResponse(a,b,c,d,e);
o.expectedRequests.push([b,c]);
};
o.assertReqs = function(count, message) {
o.requests = (o.requests || 0) + count;
ok(o.server.requests.length === o.requests,
message + "[expected [" + count + "] got [" +
(o.server.requests.length - (o.requests - count)) + "]]");
for (var i = 1; i <= count; i++) {
var req = o.server.requests[o.server.requests.length - i];
if (!req) {
break;
}
for (var j = o.expectedRequests.length - 1; j >= 0; --j) {
var expected = o.expectedRequests[j];
if (req.method === expected[0] &&
req.url.indexOf(expected[1]) !== 0)
{
o.expectedRequests.splice(j, 1);
}
}
}
var ex = o.expectedRequests.pop();
if (ex) {
ok(0, "expected [" + ex[0] + "] request for [" + ex[1] + "]");
}
};
return o;
};
test ("Post", function () {
var o = setUp(this);
// post without id
o.spy (o, "status", 405, "Post without id");
o.jio.post({}, o.f);
o.clock.tick(5000);
o.assertReqs(0, "no id -> no request");
// post non empty document
o.addFakeServerResponse("xwiki", "POST", "myFile", 201, "HTML RESPONSE");
o.spy(o, "value", {"id": "myFile", "ok": true},
"Create = POST non empty document");
o.jio.post({"_id": "myFile", "title": "hello there"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(3, "put -> 1 request to get csrf token, 1 to get doc and 1 to post data");
// post but document already exists (post = error!, put = ok)
o.answer = JSON.stringify({"_id": "myFile", "title": "hello there"});
o.addFakeServerResponse("xwiki", "GET", "myFile", 200, o.answer);
o.spy (o, "status", 409, "Post but document already exists");
o.jio.post({"_id": "myFile", "title": "hello again"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(1, "post w/ existing doc -> 1 request to get doc then fail");
o.jio.stop();
});
test ("Put", function(){
var o = setUp(this);
// put without id => id required
o.spy (o, "status", 20, "Put without id");
o.jio.put({}, o.f);
o.clock.tick(5000);
o.assertReqs(0, "put w/o id -> 0 requests");
// put non empty document
o.addFakeServerResponse("xwiki", "POST", "put1", 201, "HTML RESPONSE");
o.spy (o, "value", {"ok": true, "id": "put1"},
"Create = PUT non empty document");
o.jio.put({"_id": "put1", "title": "myPut1"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(3, "put normal doc -> 1 req to get doc, 1 for csrf token, 1 to post");
// put but document already exists = update
o.answer = JSON.stringify({"_id": "put1", "title": "myPut1"});
o.addFakeServerResponse("xwiki", "GET", "put1", 200, o.answer);
o.addFakeServerResponse("xwiki", "POST", "put1", 201, "HTML RESPONSE");
o.spy (o, "value", {"ok": true, "id": "put1"}, "Updated the document");
o.jio.put({"_id": "put1", "title": "myPut2abcdedg"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(3, "put normal doc -> 1 req to get doc, 1 for csrf token, 1 to post");
o.jio.stop();
});
test ("PutAttachment", function(){
var o = setUp(this);
// putAttachment without doc id => id required
o.spy(o, "status", 20, "PutAttachment without doc id");
o.jio.putAttachment({}, o.f);
o.clock.tick(5000);
o.assertReqs(0, "put attach w/o doc id -> 0 requests");
// putAttachment without attachment id => attachment id required
o.spy(o, "status", 22, "PutAttachment without attachment id");
o.jio.putAttachment({"_id": "putattmt1"}, o.f);
o.clock.tick(5000);
o.assertReqs(0, "put attach w/o attach id -> 0 requests");
// putAttachment without underlying document => not found
o.addFakeServerResponse("xwiki", "GET", "putattmtx", 404, "HTML RESPONSE");
o.spy(o, "status", 404, "PutAttachment without document");
o.jio.putAttachment({"_id": "putattmtx", "_attachment": "putattmt2"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(1, "put attach w/o existing document -> 1 request to get doc");
// putAttachment with document without data
o.answer = JSON.stringify({"_id": "putattmt1", "title": "myPutAttm1"});
o.addFakeServerResponse("xwiki", "GET", "putattmt1", 200, o.answer);
o.addFakeServerResponse("xwiki", "POST", "putattmt1/putattmt2", 201,"HTML"+
+ "RESPONSE");
o.spy(o, "value", {"ok": true, "id": "putattmt1/putattmt2"},
"PutAttachment with document, without data");
o.jio.putAttachment({"_id": "putattmt1", "_attachment": "putattmt2"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(3, "put attach -> 1 request to get document, 1 to put " +
"attach, 1 to get csrf token");
o.jio.stop();
});
test ("Get", function(){
var o = setUp(this);
// get inexistent document
o.spy(o, "status", 404, "Get non existing document");
o.jio.get("get1", o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(1, "try to get nonexistent doc -> 1 request");
// get inexistent attachment
o.spy(o, "status", 404, "Get non existing attachment");
o.jio.get("get1/get2", o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(1, "try to get nonexistent attach -> 1 request");
// get document
o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<page xmlns="http://www.xwiki.org"><title>some title</title></page>';
o.addFakeServerResponse("xwiki", "GET", "get3", 200, o.answer);
o.spy(o, "value", {"_id": "get3", "title": "some title"}, "Get document");
o.jio.get("get3", o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(1, "get document -> 1 request");
// get inexistent attachment (document exists)
o.spy(o, "status", 404, "Get non existing attachment (doc exists)");
o.jio.get({"_id": "get3", "_attachment": "getx"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(1, "get nonexistant attachment -> 1 request");
// get attachment
o.answer = JSON.stringify({"_id": "get4", "title": "some attachment"});
o.addFakeServerResponse("xwiki", "GET", "get3/get4", 200, o.answer);
o.spy(o, "value", {"_id": "get4", "title": "some attachment"},
"Get attachment");
o.jio.get({"_id": "get3", "_attachment": "get4"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(1, "get attachment -> 1 request");
o.jio.stop();
});
test ("Remove", function(){
var o = setUp(this);
// remove inexistent document
o.addFakeServerResponse("xwiki", "GET", "remove1", 404, "HTML RESPONSE");
o.spy(o, "status", 404, "Remove non existening document");
o.jio.remove({"_id": "remove1"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(2, "remove nonexistent doc -> 1 request for csrf and 1 for doc");
// remove inexistent document/attachment
o.addFakeServerResponse("xwiki", "GET", "remove1/remove2", 404, "HTML" +
"RESPONSE");
o.spy(o, "status", 404, "Remove inexistent document/attachment");
o.jio.removeAttachment({"_id": "remove1", "_attachment": "remove2"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(2, "remove nonexistant attach -> 1 request for csrf and 1 for doc");
// remove document
o.answer = JSON.stringify({"_id": "remove3", "title": "some doc"});
//o.addFakeServerResponse("xwiki", "GET", "remove3", 200, o.answer);
o.addFakeServerResponse("xwiki", "POST", "bin/delete/Main/remove3",
200, "HTML RESPONSE");
o.spy(o, "value", {"ok": true, "id": "remove3"}, "Remove document");
o.jio.remove({"_id": "remove3"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(2, "remove document -> 1 request for csrf and 1 for deleting doc");
o.answer = JSON.stringify({
"_id": "remove4",
"title": "some doc",
"_attachments": {
"remove5": {
"length": 4,
"digest": "md5-d41d8cd98f00b204e9800998ecf8427e"
}
}
});
// remove attachment
o.addFakeServerResponse("xwiki", "POST", "delattachment/Main/remove4/remove5",
200, "HTML RESPONSE");
o.spy(o, "value", {"ok": true, "id": "remove4/remove5"},
"Remove attachment");
o.jio.removeAttachment({"_id": "remove4", "_attachment": "remove5"}, o.f);
o.clock.tick(5000);
o.server.respond();
o.assertReqs(2, "remove attach -> 1 request for csrf and 1 for deletion");
o.jio.stop();
});
/*
test ("AllDocs", function () {
// need to make server requests before activating fakeServer
var davlist = getXML('responsexml/davlist'),
o = setUp(this);
// get allDocs, no content
addFakeServerResponse("xwiki", "PROPFIND", "", 200, davlist);
o.thisShouldBeTheAnswer = {
"rows": [
{"id": "alldocs1", "key": "alldocs1", "value": {}},
{"id": "alldocs2", "key": "alldocs2", "value": {}}
],
"total_rows": 2
}
o.spy(o, "value", o.thisShouldBeTheAnswer, "allDocs (no content)");
o.jio.allDocs(o.f);
o.clock.tick(5000);
respond();
// allDocs with option include
o.all1 = {"_id": "allDocs1", "title": "a doc title"};
o.all2 = {"_id": "allDocs2", "title": "another doc title"};
o.thisShouldBeTheAnswer = {
"rows": [
{"id": "alldocs1", "key": "alldocs1", "value": {}, "doc": o.all1},
{"id": "alldocs2", "key": "alldocs2", "value": {}, "doc": o.all2}
],
"total_rows": 2
}
addFakeServerResponse("xwiki", "GET", "alldocs1", 200,
JSON.stringify(o.all1));
addFakeServerResponse("xwiki", "GET", "alldocs2", 200,
JSON.stringify(o.all2));
o.spy(o, "value", o.thisShouldBeTheAnswer, "allDocs (include_docs)");
o.jio.allDocs({"include_docs":true}, o.f);
o.clock.tick(5000);
respond();
o.jio.stop();
});
*/
var nThen = function(next) {
var funcs = [];
var calls = 0;
var waitFor = function(func) {
calls++;
return function() {
if (func) {
func.apply(null, arguments);
}
calls = (calls || 1) - 1;
while (!calls && funcs.length) {
funcs.shift()(waitFor);
}
};
};
next(waitFor);
var ret = {
nThen: function(next) {
funcs.push(next);
return ret;
},
orTimeout: function(func, milliseconds) {
var cto;
var timeout = setTimeout(function() {
while (funcs.shift() !== cto) ;
func(waitFor);
calls = (calls || 1) - 1;
while (!calls && funcs.length) { console.log("call"); funcs.shift()(waitFor); }
}, milliseconds);
funcs.push(cto = function() { clearTimeout(timeout); });
return ret;
}
};
return ret;
};
if (window.location.href.match(/xwiki\/bin\/view/)) (function() {
test ("XWiki Live Server setup", function () {
var o = setUp(this);
o.jio.stop();
this.sandbox.restore();
o.jio.start();
QUnit.stop();
nThen(function(waitFor) {
// Remove the document if it exists.
o.jio.remove({"_id": "one.json"}, waitFor());
}).nThen(function(waitFor) {
// post a new document
o.spy(o, "value", {"id": "one.json", "ok": true}, "Live post document");
o.jio.post({"_id": "one.json", "title": "hello"}, waitFor(o.f));
}).nThen(function(waitFor) {
o.jio.get("one.json", waitFor(function(err, ret) {
ok(!err);
ok(ret._id == "one.json");
ok(ret.title == "hello");
}));
}).nThen(function(waitFor) {
// modify document
o.spy(o, "value", {"id": "one.json", "ok": true}, "Live modify document");
o.jio.put({"_id": "one.json", "title": "hello modified"}, waitFor(o.f));
}).nThen(function(waitFor) {
o.jio.get("one.json", waitFor(function(err, ret) {
ok(!err);
ok(ret.title == "hello modified");
}));
}).nThen(function(waitFor) {
// add attachment
o.spy(o, "value", {"id": "one.json/att.txt", "ok": true}, "Put attachment");
o.jio.putAttachment({
"_id": "one.json",
"_attachment": "att.txt",
"_mimetype": "text/plain",
"_data": "there2"
}, waitFor(o.f));
}).nThen(function(waitFor) {
// test allDocs
/*o.jio.allDocs({"include_docs":true},
function(s){console.log(s);},
function ( e ) {console.log(e);
}, o.f);*/
}).nThen(function(waitFor) {
// get Attachment
o.jio.getAttachment({"_id":"one.json", "_attachment":"att.txt"}, waitFor(function(err, ret) {
ok(!err);
var fr = new FileReader();
fr.onload = waitFor(function(dat) {
ok(dat.target.result == "there2");
});
fr.readAsText(ret);
}));
}).nThen(function(waitFor) {
// remove Attachment
o.spy(o, "value", {"id": "one.json/att.txt", "ok": true}, "Remove attachment");
o.jio.removeAttachment({"_id":"one.json","_attachment":"att.txt"}, waitFor(o.f));
}).nThen(function(waitFor) {
// remove Document
o.spy(o, "value", {"id": "one.json", "ok": true}, "Remove document");
o.jio.remove("one.json", waitFor(o.f));
}).nThen(function(waitFor) {
//console.log("success");
}).orTimeout(function() {
//console.log("failed");
ok(0);
}, 15000).nThen(function() {
//console.log("complete");
o.jio.stop();
QUnit.start();
});
});
})(); // Live XWiki
})(); // xwiki
}; // end thisfun }; // end thisfun
if (window.requirejs) { if (window.requirejs) {
......
...@@ -33,6 +33,8 @@ ...@@ -33,6 +33,8 @@
</script> </script>
<script type="text/javascript" src="../complex_queries.js"></script> <script type="text/javascript" src="../complex_queries.js"></script>
</script> </script>
<script type="text/javascript" src="../src/jio.storage/xwikistorage.js">
</script>
<script type="text/javascript" src="./jiotests.js"></script> <script type="text/javascript" src="./jiotests.js"></script>
</body> </body>
</html> </html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment