Commit 176f9543 authored by Caleb James DeLisle's avatar Caleb James DeLisle Committed by Tristan Cavelier

Updated XWiki storage and tests to fit new version of JIO

parent e79f94f9
/*jslint indent: 2, /*jslint
indent: 2,
maxlen: 80, maxlen: 80,
nomen: true plusplus: true,
nomen: true,
regexp: true
*/ */
/*global /*global
define: true, define: true,
exports: true,
require: true,
jIO: true, jIO: true,
jQuery: true, jQuery: true,
XMLHttpRequest: true,
Blob: true,
FormData: true,
window: true, window: true,
setTimeout: true XMLHttpRequest,
FormData
*/ */
/** /**
* JIO XWiki Storage. Type = 'xwiki'. * JIO XWiki Storage. Type = 'xwiki'.
* XWiki Document/Attachment storage. * XWiki Document/Attachment storage.
*/ */
(function () { (function (dependencies, module) {
"use strict"; "use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(require('jio'), require('jquery'));
}
module(jIO, jQuery);
}([
'jio',
'jquery'
], function (jIO, $) {
"use strict";
function detectWiki() {
// try first the meta tag, then look for js,
// then finally fail over to 'xwiki'...
return $('meta[name="wiki"]').attr('content') ||
(window.XWiki || {}).currentWiki ||
'xwiki';
}
function detectXWikiURL(wiki) {
var loc, action, idx;
loc = window.location.href;
action = (window.XWiki || {}).contextAction || 'view';
idx = loc.indexOf('/wiki/' + wiki + '/' + action + '/');
if (idx !== -1) {
return loc.substring(0, idx);
}
idx = loc.indexOf('/bin/' + action + '/');
if (idx !== -1) {
// single wiki host:port/xwiki/bin/view/Main/WebHome
return loc.substring(0, idx);
}
throw new Error("Unable to detect XWiki URL");
}
/**
* Checks if an object has no enumerable keys
*
* @param {Object} obj The object
* @return {Boolean} true if empty, else false
*/
function objectIsEmpty(obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
}
var $, store; function detectIsPathBasedMultiwiki(xwikiurl) {
store = function (spec, my) { var loc = window.location.href;
if (loc.indexOf(xwikiurl + '/wiki/') === 0) { return true; }
if (loc.indexOf(xwikiurl + '/bin/') === 0) { return false; }
// warn the user that we're unusure?
return false;
}
/**
* The JIO XWikiStorage extension
*
* @class XWikiStorage
* @constructor
*/
function XWikiStorage(spec) {
spec = spec || {}; spec = spec || {};
var that, priv, xwikistorage; var priv = {};
// the wiki to store stuff in
priv.wiki = spec.wiki || detectWiki();
// URL location of the wiki
// XWiki doesn't currently allow cross-domain requests.
priv.xwikiurl = (spec.xwikiurl !== undefined)
? spec.xwikiurl : detectXWikiURL(priv.wiki);
// Which URL to load for getting the Anti-CSRF form token, used for testing.
priv.formTokenPath = spec.formTokenPath || priv.xwikiurl;
that = my.basicStorage(spec, my); priv.pathBasedMultiwiki = (spec.pathBasedMultiwiki !== undefined)
priv = {}; ? spec.pathBasedMultiwiki : detectIsPathBasedMultiwiki(priv.xwikiurl);
/** /**
* Get the Space and Page components of a documkent ID. * Get the Space and Page components of a documkent ID.
...@@ -35,6 +114,7 @@ ...@@ -35,6 +114,7 @@
* @return a map of { 'space':<Space>, 'page':<Page> } * @return a map of { 'space':<Space>, 'page':<Page> }
*/ */
priv.getParts = function (id) { priv.getParts = function (id) {
if (id.indexOf('/') === -1) { if (id.indexOf('/') === -1) {
return { return {
space: 'Main', space: 'Main',
...@@ -103,37 +183,21 @@ ...@@ -103,37 +183,21 @@
+ priv.wiki + '/spaces/' + parts.space + '/pages/' + parts.page; + priv.wiki + '/spaces/' + parts.space + '/pages/' + parts.page;
}; };
/** priv.getURL = function (action, space, page) {
* Make an HTML5 Blob object. var out = [priv.xwikiurl];
* Equivilant to the `new Blob()` constructor. if (!priv.pathBasedMultiwiki) {
* Will fall back on deprecated BlobBuilder if necessary. out.push('bin');
*/ } else {
priv.makeBlob = function (contentArray, options) { out.push('wiki', priv.wiki);
var i, bb, BB;
try {
// use the constructor if possible.
return new Blob(contentArray, options);
} catch (err) {
// fall back on the blob builder.
BB = (window.MozBlobBuilder || window.WebKitBlobBuilder
|| window.BlobBuilder);
bb = new BB();
for (i = 0; i < contentArray.length; i += 1) {
bb.append(contentArray[i]);
}
return bb.getBlob(options ? options.type : undefined);
} }
}; out.push(action, space, page);
return out.join('/');
priv.isBlob = function (potentialBlob) {
return potentialBlob !== undefined &&
potentialBlob.toString() === "[object Blob]";
}; };
/* /*
* Wrapper for the xwikistorage based on localstorage JiO store. * Wrapper for the xwikistorage based on localstorage JiO store.
*/ */
xwikistorage = { priv._storage = this._storage = {
/** /**
* Get content of an XWikiDocument. * Get content of an XWikiDocument.
* *
...@@ -143,7 +207,60 @@ ...@@ -143,7 +207,60 @@
*/ */
getItem: function (docId, andThen) { getItem: function (docId, andThen) {
var success = function (jqxhr) { function attachSuccess(doc, jqxhr) {
var out = {}, xd;
if (jqxhr.status !== 200) {
andThen(null, {
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": "",
"message": "Failed to get attachments for document [" +
docId + "]",
"reason": ""
});
return;
}
try {
xd = $(jqxhr.responseText);
xd.find('attachment').each(function () {
var attach = {}, attachXML = $(this);
attachXML.find('mimeType').each(function () {
attach.content_type = $(this).text();
});
attachXML.find('size').each(function () {
attach.length = Number($(this).text());
});
attach.digest = "unknown-0";
attachXML.find('name').each(function () {
out[$(this).text()] = attach;
});
});
doc._attachments = out;
andThen(doc, null);
} catch (err) {
andThen(null, {
status: 500,
statusText: "internal error",
error: err,
message: err.message,
reason: ""
});
}
}
function getAttachments(doc) {
$.ajax({
url: priv.getDocRestURL(docId) + '/attachments',
type: "GET",
async: true,
dataType: 'xml',
complete: function (jqxhr) {
attachSuccess(doc, jqxhr);
}
});
}
function success(jqxhr) {
var out, xd; var out, xd;
out = {}; out = {};
try { try {
...@@ -165,7 +282,7 @@ ...@@ -165,7 +282,7 @@
out.content = $(this).text(); out.content = $(this).text();
}); });
out._id = docId; out._id = docId;
andThen(out, null); getAttachments(out);
} catch (err) { } catch (err) {
andThen(null, { andThen(null, {
status: 500, status: 500,
...@@ -175,7 +292,7 @@ ...@@ -175,7 +292,7 @@
reason: "" reason: ""
}); });
} }
}; }
$.ajax({ $.ajax({
url: priv.getDocRestURL(docId), url: priv.getDocRestURL(docId),
...@@ -184,7 +301,7 @@ ...@@ -184,7 +301,7 @@
dataType: 'xml', dataType: 'xml',
// Use complete instead of success and error because phantomjs // Use complete instead of success and error because phantomjs
// sometimes causes error to be called with html return code 200. // sometimes causes error to be called with http return code 200.
complete: function (jqxhr) { complete: function (jqxhr) {
if (jqxhr.status === 404) { if (jqxhr.status === 404) {
andThen(null, null); andThen(null, null);
...@@ -217,14 +334,10 @@ ...@@ -217,14 +334,10 @@
// need to do this manually, jquery doesn't support returning blobs. // need to do this manually, jquery doesn't support returning blobs.
xhr = new XMLHttpRequest(); xhr = new XMLHttpRequest();
parts = priv.getParts(docId); parts = priv.getParts(docId);
url = priv.xwikiurl + '/bin/download/' + parts.space + url = priv.getURL('download', parts.space, parts.page) +
"/" + parts.page + "/" + fileName + '?cb=' + Math.random(); '/' + fileName + '?cb=' + Math.random();
xhr.open('GET', url, true); xhr.open('GET', url, true);
if (priv.useBlobs) {
xhr.responseType = 'blob'; xhr.responseType = 'blob';
} else {
xhr.responseType = 'text';
}
xhr.onload = function () { xhr.onload = function () {
if (xhr.status === 200) { if (xhr.status === 200) {
...@@ -259,13 +372,12 @@ ...@@ -259,13 +372,12 @@
setItem: function (id, doc, andThen) { setItem: function (id, doc, andThen) {
priv.doWithFormToken(function (formToken, err) { priv.doWithFormToken(function (formToken, err) {
if (err) { if (err) {
that.error(err); andThen(err);
return; return;
} }
var parts = priv.getParts(id); var parts = priv.getParts(id);
$.ajax({ $.ajax({
url: priv.xwikiurl + "/bin/preview/" + url: priv.getURL('preview', parts.space, parts.page),
parts.space + '/' + parts.page,
type: "POST", type: "POST",
async: true, async: true,
dataType: 'text', dataType: 'text',
...@@ -308,26 +420,26 @@ ...@@ -308,26 +420,26 @@
* @param docId the ID of the document to attach to. * @param docId the ID of the document to attach to.
* @param fileName the attachment file name. * @param fileName the attachment file name.
* @param mimeType the MIME type of the attachment content. * @param mimeType the MIME type of the attachment content.
* @param content the attachment content. * @param blob the attachment content.
* @param andThen a callback taking one parameter, the error if any. * @param andThen a callback taking one parameter, the error if any.
*/ */
setAttachment: function (docId, fileName, mimeType, content, andThen) { setAttachment: function (docId, fileName, blob, andThen) {
priv.doWithFormToken(function (formToken, err) { priv.doWithFormToken(function (formToken, err) {
var parts, blob, fd, xhr; var parts, fd, xhr;
if (err) { if (err) {
that.error(err); andThen(err);
return; return;
} }
parts = priv.getParts(docId); parts = priv.getParts(docId);
blob = priv.isBlob(content)
? content
: priv.makeBlob([content], {type: mimeType});
fd = new FormData(); fd = new FormData();
fd.append("filepath", blob, fileName); fd.append("filepath", blob, fileName);
fd.append("form_token", formToken); fd.append("form_token", formToken);
xhr = new XMLHttpRequest(); xhr = new XMLHttpRequest();
xhr.open('POST', priv.xwikiurl + "/bin/upload/" + xhr.open(
parts.space + '/' + parts.page, true); 'POST',
priv.getURL('upload', parts.space, parts.page),
true
);
xhr.onload = function () { xhr.onload = function () {
if (xhr.status === 302 || xhr.status === 200) { if (xhr.status === 302 || xhr.status === 200) {
andThen(null); andThen(null);
...@@ -349,13 +461,12 @@ ...@@ -349,13 +461,12 @@
removeItem: function (id, andThen) { removeItem: function (id, andThen) {
priv.doWithFormToken(function (formToken, err) { priv.doWithFormToken(function (formToken, err) {
if (err) { if (err) {
that.error(err); andThen(err);
return; return;
} }
var parts = priv.getParts(id); var parts = priv.getParts(id);
$.ajax({ $.ajax({
url: priv.xwikiurl + "/bin/delete/" + url: priv.getURL('delete', parts.space, parts.page),
parts.space + '/' + parts.page,
type: "POST", type: "POST",
async: true, async: true,
dataType: 'text', dataType: 'text',
...@@ -383,12 +494,12 @@ ...@@ -383,12 +494,12 @@
var parts = priv.getParts(docId); var parts = priv.getParts(docId);
priv.doWithFormToken(function (formToken, err) { priv.doWithFormToken(function (formToken, err) {
if (err) { if (err) {
that.error(err); andThen(err);
return; return;
} }
$.ajax({ $.ajax({
url: priv.xwikiurl + "/bin/delattachment/" + parts.space + '/' + url: priv.getURL('delattachment', parts.space, parts.page) +
parts.page + '/' + fileName, '/' + fileName,
type: "POST", type: "POST",
async: true, async: true,
dataType: 'text', dataType: 'text',
...@@ -411,322 +522,594 @@ ...@@ -411,322 +522,594 @@
} }
}); });
}); });
} },
};
// ==================== Tools ====================
/** /**
* Update [doc] the document object and remove [doc] keys * Gets a document list from the xwiki storage.
* which are not in [new_doc]. It only changes [doc] keys not starting * It will retreive an array containing files meta data owned by
* with an underscore. * the user.
* ex: doc: {key:value1,_key:value2} with * @method allDocs
* 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) { allDocs: function (includeDocs, andThen) {
var k; var getData = function (callback) {
for (k in doc) { $.ajax({
if (doc.hasOwnProperty(k)) { url: priv.xwikiurl + '/rest/wikis/xwiki/pages?cb=' + Date.now(),
if (k[0] !== '_') { type: "GET",
delete doc[k]; async: true,
} dataType: 'xml',
} success: function (xmlData) {
} var data = [];
for (k in new_doc) { $(xmlData).find('fullName').each(function () {
if (new_doc.hasOwnProperty(k)) { data[data.length] = $(this).text();
if (k[0] !== '_') { });
doc[k] = new_doc[k]; callback(data);
} },
} error: function (error) {
andThen(null, error);
} }
});
}; };
/** getData(function (rows, err) {
* Checks if an object has no enumerable keys var i, next;
* @method objectIsEmpty next = function (i) {
* @param {object} obj The object priv._storage.getItem(rows[i].id, function (doc, err) {
* @return {boolean} true if empty, else false if (err) {
*/ andThen(null, err);
priv.objectIsEmpty = function (obj) { return;
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
} }
rows[i].doc = doc;
if (i < rows.length) {
next(i + 1);
} else {
andThen(rows);
} }
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;
// If true then Blob objects will be returned by
// getAttachment() rather than strings.
priv.useBlobs = spec.useBlobs || false;
// If true then Blob objects will be returned by
// getAttachment() rather than strings.
priv.useBlobs = spec.useBlobs || false;
that.specToStore = function () {
return {
"username": priv.username,
"language": priv.language,
"xwikiurl": priv.xwikiurl
}; };
if (err) {
return andThen(null, err);
}
for (i = 0; i < rows.length; i++) {
rows[i] = {
id: rows[i],
key: rows[i],
value: {}
}; };
}
// can't fo wrong since no parameters are required. if (includeDocs) {
that.validateState = function () { next(0);
return ''; } else {
andThen(rows);
}
});
}
}; };
}
// ==================== commands ====================
/** /**
* Create a document in local storage. * Create a document in local storage.
*
* @method post * @method post
* @param {object} command The JIO command * @param {Object} command The JIO command
* @param {Object} metadata The metadata to store
* @param {Object} options The command options
*/ */
that.post = function (command) { XWikiStorage.prototype.post = function (command, metadata) {
var docId = command.getDocId(); var doc_id = metadata._id, that = this;
if (!(typeof docId === "string" && docId !== "")) { if (!doc_id) {
setTimeout(function () { doc_id = jIO.util.generateUuid();
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) { that._storage.getItem(doc_id, function (doc, err) {
if (err) { if (err) {
that.error(err); command.error(err);
} else if (doc === null) { return;
}
if (doc === null) {
// the document does not exist // the document does not exist
xwikistorage.setItem(command.getDocId(), doc = jIO.util.deepClone(metadata);
command.cloneDoc(), doc._id = doc_id;
function (err) { delete doc._attachments;
that._storage.setItem(doc_id, doc, function (err) {
if (err) { if (err) {
that.error(err); command.error(
"failed",
"failed to upload document",
String(err)
);
} else { } else {
that.success({ command.success({"id": doc_id});
"ok": true,
"id": command.getDocId()
});
} }
}); });
} else { } else {
// the document already exists // the document already exists
that.error({ command.error(
"status": 409, "conflict",
"statusText": "Conflicts", "document exists",
"error": "conflicts", "Cannot create a new document"
"message": "Cannot create a new document", );
"reason": "Document already exists (use 'put' to modify it)"
});
} }
}); });
}; };
/** /**
* Create or update a document in local storage. * Create or update a document in local storage.
*
* @method put * @method put
* @param {object} command The JIO command * @param {Object} command The JIO command
* @param {Object} metadata The metadata to store
* @param {Object} options The command options
*/ */
that.put = function (command) { XWikiStorage.prototype.put = function (command, metadata) {
xwikistorage.getItem(command.getDocId(), function (doc, err) { var tmp, status, that = this;
that._storage.getItem(metadata._id, function (doc, err) {
if (err) { if (err) {
that.error(err); command.error(err);
} else if (doc === null) { return;
doc = command.cloneDoc();
} else {
priv.documentObjectUpdate(doc, command.cloneDoc());
} }
// write if (doc === null || doc === undefined) {
xwikistorage.setItem(command.getDocId(), doc, function (err) { // the document does not exist
if (err) { doc = jIO.util.deepClone(metadata);
that.error(err); delete doc._attachments;
status = "created";
} else { } else {
that.success({ // the document already exists
"ok": true, tmp = jIO.util.deepClone(metadata);
"id": command.getDocId() tmp._attachments = doc._attachments;
}); doc = tmp;
status = "no_content";
} }
// write
that._storage.setItem(metadata._id, doc, function (err) {
if (err) { command.error(err); return; }
command.success(status);
}); });
}); });
}; };
/** /**
* Add an attachment to a document * Add an attachment to a document
*
* @method putAttachment * @method putAttachment
* @param {object} command The JIO command * @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/ */
that.putAttachment = function (command) { XWikiStorage.prototype.putAttachment = function (command, param) {
xwikistorage.getItem(command.getDocId(), function (doc, err) { var that = this, status = "created";
that._storage.getItem(param._id, function (doc, err) {
if (err) { if (err) {
that.error(err); return command.error(err);
} else if (doc === null) { }
if (doc === null) {
// the document does not exist // the document does not exist
that.error({ return command.error(
"status": 404, "not_found",
"statusText": "Not Found", "missing",
"error": "not_found", "Impossible to add attachment"
"message": "Impossible to add attachment", );
"reason": "Document not found" }
});
} else { // the document already exists
// Document exists, upload attachment. // download data
xwikistorage.setAttachment(command.getDocId(), if ((doc._attachments || {})[param._attachment]) {
command.getAttachmentId(), status = "no_content";
command.getAttachmentMimeType(), }
command.getAttachmentData(), that._storage.setAttachment(param._id,
param._attachment,
param._blob,
function (err) { function (err) {
if (err) { if (err) {
that.error(err); command.error(err);
} else { } else {
that.success({ // XWiki doesn't do digests of attachments
"ok": true, // so we'll calculate it on the client side.
"id": command.getDocId() + "/" + command.getAttachmentId() jIO.util.readBlobAsBinaryString(param._blob).then(function (e) {
command.success(status,
{"digest": jIO.util.makeBinaryStringDigest(e.target.result)}
);
}); });
} }
}); });
}
}); });
}; };
/** /**
* Get a document or attachment * Get a document
*
* @method get * @method get
* @param {object} command The JIO command * @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/ */
that.get = that.getAttachment = function (command) { XWikiStorage.prototype.get = function (command, param) {
if (typeof command.getAttachmentId() === "string") { this._storage.getItem(param._id, function (ret, err) {
// seeking for an attachment if (err) { command.error(err); return; }
xwikistorage.getAttachment(command.getDocId(), if (ret === null) {
command.getAttachmentId(), command.error(
function (attach, err) { "not_found",
if (err) { "missing",
that.error(err); "Cannot find document"
} else if (attach !== null) { );
that.success(attach);
} else { } else {
that.error({ command.success({"data": ret});
"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) { /**
* Get an attachment
*
* @method getAttachment
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.getAttachment = function (command, param) {
var that = this;
that._storage.getItem(param._id, function (doc, err) {
if (err) { if (err) {
that.error(err); return command.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"
});
} }
}); if (doc === null) {
return command.error(
"not_found",
"missing document",
"Cannot find document"
);
}
if (typeof doc._attachments !== 'object' ||
typeof doc._attachments[param._attachment] !== 'object') {
return command.error(
"not_found",
"missing attachment",
"Cannot find attachment"
);
} }
that._storage.getAttachment(param._id, param._attachment,
function (blob, err) {
var attach = doc._attachments[param._attachment];
if (err) {
return command.error(err);
}
if (blob.size !== attach.length) {
return command.error(
"incomplete",
"attachment size incorrect",
"expected [" + attach.size + "] bytes, got [" + blob.size + "]"
);
}
command.success({
"data": blob,
"digest": attach.digest || "",
"content_type": attach.content_type || ""
});
});
});
}; };
/** /**
* Remove a document or attachment * Remove a document
*
* @method remove * @method remove
* @param {object} command The JIO command * @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/ */
that.remove = that.removeAttachment = function (command) { XWikiStorage.prototype.remove = function (command, param) {
var objId, complete; this._storage.removeItem(param._id, function (err) {
// notFoundError = function (word) {
// that.error({
// "status": 404,
// "statusText": "Not Found",
// "error": "not_found",
// "message": word + " not found",
// "reason": "missing"
// });
// };
objId = command.getDocId();
complete = function (err) {
if (err) { if (err) {
that.error(err); command.error(err);
} else { } else {
that.success({ command.success();
"ok": true,
"id": objId
});
} }
});
}; };
if (typeof command.getAttachmentId() === "string") {
objId += '/' + command.getAttachmentId(); /**
xwikistorage.removeAttachment(command.getDocId(), * Remove an attachment
command.getAttachmentId(), *
complete); * @method removeAttachment
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.removeAttachment = function (command, param) {
var that = this;
that._storage.getItem(param._id, function (doc, err) {
if (err) {
return command.error(err);
}
if (typeof doc !== 'object' || doc === null) {
return command.error(
"not_found",
"missing document",
"Document not found"
);
}
if (typeof doc._attachments !== "object" ||
typeof doc._attachments[param._attachment] !== "object") {
return command.error(
"not_found",
"missing attachment",
"Attachment not found"
);
}
that._storage.removeAttachment(param._id, param._attachment,
function (err) {
if (err) {
command.error(err);
} else { } else {
xwikistorage.removeItem(objId, complete); command.success();
} }
});
});
}; };
/** /**
* Get all filenames belonging to a user from the document index * Get all filenames belonging to a user from the document index
*
* @method allDocs * @method allDocs
* @param {object} command The JIO command * @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/ */
that.allDocs = function () { XWikiStorage.prototype.allDocs = function (command, param, options) {
setTimeout(function () { var i, document_list, document_object, delete_id, that = this;
that.error({ param.unused = true;
"status": 405, document_list = [];
"statusText": "Method Not Allowed", if (options.query === undefined && options.sort_on === undefined &&
"error": "method_not_allowed", options.select_list === undefined &&
"message": "Your are not allowed to use this command", options.include_docs === undefined) {
"reason": "xwikistorage forbids AllDocs command executions"
that._storage.allDocs(options.include_docs, function (rows, err) {
if (err) {
return command.error(err);
}
command.success({"data": {"rows": rows, "total_rows": rows.length}});
}); });
} else {
that._storage.allDocs(true, function (rows, err) {
if (err) {
return command.error(err);
}
for (i = 0; i < rows.length; i++) {
document_list.push(rows[i].doc);
}
});
options.select_list = options.select_list || [];
if (options.select_list.indexOf("_id") === -1) {
options.select_list.push("_id");
delete_id = true;
}
if (options.include_docs === true) {
document_object = {};
document_list.forEach(function (meta) {
document_object[meta._id] = meta;
});
}
jIO.QueryFactory.create(options.query || "", this._key_schema).
exec(document_list, options).then(function () {
document_list = document_list.map(function (value) {
var o = {
"id": value._id,
"key": value._id
};
if (options.include_docs === true) {
o.doc = document_object[value._id];
delete document_object[value._id];
}
if (delete_id) {
delete value._id;
}
o.value = value;
return o;
}); });
command.success({"data": {
"total_rows": document_list.length,
"rows": document_list
}});
});
}
}; };
return that; /**
* Check the storage or a specific document
*
* @method check
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.check = function (command, param) {
this.genericRepair(command, param, false);
}; };
if (typeof define === 'function' && define.amd) { /**
define(['jquery', 'jio'], function (jquery, jIO) { * Repair the storage or a specific document
$ = jquery; *
jIO.addStorageType('xwiki', store); * @method repair
}); * @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.repair = function (command, param) {
this.genericRepair(command, param, true);
};
/**
* A generic method that manage check or repair command
*
* @method genericRepair
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Boolean} repair If true then repair else just check
*/
XWikiStorage.prototype.genericRepair = function (command, param, repair) {
var that = this, final_result;
function referenceAttachment(param, attachment) {
if (param.referenced_attachments.indexOf(attachment) !== -1) {
return;
}
var i = param.unreferenced_attachments.indexOf(attachment);
if (i !== -1) {
param.unreferenced_attachments.splice(i, 1);
}
param.referenced_attachments[param.referenced_attachments.length] =
attachment;
}
function attachmentFound(param, attachment) {
if (param.referenced_attachments.indexOf(attachment) !== -1) {
return;
}
if (param.unreferenced_attachments.indexOf(attachment) !== -1) {
return;
}
param.unreferenced_attachments[param.unreferenced_attachments.length] =
attachment;
}
function repairOne(param, repair) {
var i, doc, modified;
doc = that._storage.getItem(param._id);
if (doc === null) {
return; // OK
}
// check document type
if (typeof doc !== 'object' || doc === null) {
// wrong document
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Document is unrecoverable"
]};
}
// delete the document
that._storage.removeItem(param._id);
return; // OK
}
// good document type
// repair json document
if (!repair) {
if (!(new jIO.Metadata(doc).check())) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Some metadata might be lost"
]};
}
} else {
modified = jIO.util.uniqueJSONStringify(doc) !==
jIO.util.uniqueJSONStringify(new jIO.Metadata(doc).format()._dict);
}
if (doc._attachments !== undefined) {
if (typeof doc._attachments !== 'object') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Attachments are unrecoverable"
]};
}
delete doc._attachments;
that._storage.setItem(param._id, doc);
return; // OK
}
for (i in doc._attachments) {
if (doc._attachments.hasOwnProperty(i)) {
// check attachment existence
if (that._storage.getItem(param._id + "/" + i) !== 'string') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"missing attachment",
"Attachment \"" + i + "\" of \"" + param._id + "\" is missing"
]};
}
delete doc._attachments[i];
if (objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
modified = true;
} else { } else {
jIO.addStorageType('xwiki', store); // attachment exists
$ = jQuery; // check attachment metadata
// check length
referenceAttachment(param, param._id + "/" + doc._attachments[i]);
if (doc._attachments[i].length !== undefined &&
typeof doc._attachments[i].length !== 'number') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Attachment metadata length corrupted"
]};
}
// It could take a long time to get the length, no repair.
// length can be omited
delete doc._attachments[i].length;
}
// It could take a long time to regenerate the hash, no check.
// Impossible to discover the attachment content type.
}
}
}
} }
if (modified) {
that._storage.setItem(param._id, doc);
}
// OK
}
function repairAll(param, repair) {
var i, result;
for (i in that._database) {
if (that._database.hasOwnProperty(i)) {
// browsing every entry
// is part of the user space
if (/^[^\/]+\/[^\/]+$/.test(i)) {
// this is an attachment
attachmentFound(param, i);
} else if (/^[^\/]+$/.test(i)) {
// this is a document
param._id = i;
result = repairOne(param, repair);
if (result) {
return result;
}
} else {
// this is pollution
that._storage.removeItem(i);
}
}
}
// remove unreferenced attachments
for (i = 0; i < param.unreferenced_attachments.length; i += 1) {
that._storage.removeItem(param.unreferenced_attachments[i]);
}
}
param.referenced_attachments = [];
param.unreferenced_attachments = [];
if (typeof param._id === 'string') {
final_result = repairOne(param, repair) || {};
} else {
final_result = repairAll(param, repair) || {};
}
if (final_result.error) {
return command.error.apply(command, final_result.answers || []);
}
command.success.apply(command, final_result.answers || []);
};
jIO.addStorage('xwiki', XWikiStorage);
}()); }));
/*jslint indent: 2, maxlen: 80, nomen: true */ /*jslint
/*global define, jIO, jio_tests, test, ok, sinon, module */ indent: 2,
maxlen: 80,
plusplus: true,
nomen: true,
regexp: true
*/
/*global
define,
jIO,
test,
ok,
sinon,
module,
clearTimeout,
setTimeout,
start,
stop,
deepEqual
*/
// define([module_name], [dependencies], module); // define([module_name], [dependencies], module);
(function (dependencies, module) { (function (dependencies, module) {
...@@ -7,34 +25,103 @@ ...@@ -7,34 +25,103 @@
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
return define(dependencies, module); return define(dependencies, module);
} }
module(jIO, jio_tests); module(jIO);
}(['jio', 'jio_tests', 'xwikistorage'], function (jIO, util) { }(['jio', 'xwikistorage'], function (jIO) {
"use strict"; "use strict";
function generateTools() { function nThen(next) {
var funcs = [],
timeouts = [],
calls = 0,
abort,
ret,
waitFor;
waitFor = function (func) {
calls++;
return function () {
if (func) {
func.apply(null, arguments);
}
calls = (calls || 1) - 1;
while (!calls && funcs.length && !abort) {
funcs.shift()(waitFor);
}
};
};
waitFor.abort = function () {
timeouts.forEach(clearTimeout);
abort = 1;
};
ret = {
nThen: function (next) {
if (!abort) {
if (!calls) {
next(waitFor);
} else {
funcs.push(next);
}
}
return ret;
},
orTimeout: function (func, milliseconds) {
var cto, timeout;
if (abort) { return ret; }
if (!milliseconds) {
throw new Error("Must specify milliseconds to orTimeout()");
}
timeout = setTimeout(function () {
var f;
while (f !== cto) { f = funcs.shift(); }
func(waitFor);
calls = (calls || 1) - 1;
while (!calls && funcs.length) { funcs.shift()(waitFor); }
}, milliseconds);
cto = function () {
var i;
for (i = 0; i < timeouts.length; i++) {
if (timeouts[i] === timeout) {
timeouts.splice(i, 1);
clearTimeout(timeout);
return;
}
}
throw new Error('timeout not listed in array');
};
funcs.push(cto);
timeouts.push(timeout);
return ret;
}
};
return ret.nThen(next);
}
module('XWikiStorage');
function setUp() {
var o = { var o = {
clock: sinon.useFakeTimers(), sinon: sinon.sandbox.create()
spy: util.ospy,
tick: util.otick
}; };
function addFakeServerResponse(type, method, path, status, response) { o.addFakeServerResponse =
function (type, method, path, status, response, t) {
t = t || 'application/xml';
/*jslint unparam: true */ /*jslint unparam: true */
o.server.respondWith(method, new RegExp(path), [ o.sinon.server.respondWith(method, new RegExp(path), [
status, status,
{"Content-Type": 'application/xml'}, {"Content-Type": t},
response response
]); ]);
} };
o.addFakeServerResponse = addFakeServerResponse; o.sinon.useFakeTimers();
return o; o.sinon.useFakeServer();
}
module('XWikiStorage'); o.respond = function () {
o.sinon.clock.tick(5000);
o.sinon.server.respond();
};
function setUp(that) { o.jio = jIO.createJIO(
var o = generateTools(that); {type: 'xwiki', formTokenPath: 'form_token', xwikiurl: ''}
o.server = sinon.fakeServer.create(); );
o.jio = jIO.newJio({type: 'xwiki', formTokenPath: 'form_token'});
o.addFakeServerResponse("xwiki", "GET", "form_token", 200, o.addFakeServerResponse("xwiki", "GET", "form_token", 200,
'<meta name="form_token" content="OMGHAX"/>'); '<meta name="form_token" content="OMGHAX"/>');
o._addFakeServerResponse = o.addFakeServerResponse; o._addFakeServerResponse = o.addFakeServerResponse;
...@@ -46,11 +133,11 @@ ...@@ -46,11 +133,11 @@
o.assertReqs = function (count, message) { o.assertReqs = function (count, message) {
var i, j, req, expected, ex; var i, j, req, expected, ex;
o.requests = (o.requests || 0) + count; o.requests = (o.requests || 0) + count;
ok(o.server.requests.length === o.requests, ok(o.sinon.server.requests.length === o.requests,
message + "[expected [" + count + "] got [" + message + "[expected [" + count + "] got [" +
(o.server.requests.length - (o.requests - count)) + "]]"); (o.sinon.server.requests.length - (o.requests - count)) + "]]");
for (i = 1; i <= count; i += 1) { for (i = 1; i <= count; i += 1) {
req = o.server.requests[o.server.requests.length - i]; req = o.sinon.server.requests[o.sinon.server.requests.length - i];
if (!req) { if (!req) {
break; break;
} }
...@@ -70,227 +157,559 @@ ...@@ -70,227 +157,559 @@
return o; return o;
} }
function nThenTest(o, nt) {
stop();
nt.nThen(function () {
o.sinon.restore();
start();
}).orTimeout(function () {
o.sinon.restore();
ok(0);
start();
}, 1000);
}
test("Post", function () { test("Post", function () {
var o = setUp(this); var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// 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 // post non empty document
o.addFakeServerResponse("xwiki", "POST", "myFile", 201, "HTML RESPONSE"); o.addFakeServerResponse("xwiki", "POST", "myFile1", 201, "HTML RESPONSE");
o.spy(o, "value", {"id": "myFile", "ok": true}, o.jio.post({"_id": "myFile1", "title": "hello there"}).
"Create = POST non empty document"); then(waitFor(function (ret) {
o.jio.post({"_id": "myFile", "title": "hello there"}, o.f); deepEqual({
o.clock.tick(5000); id: "myFile1",
o.server.respond(); method: "post",
result: "success",
status: 201,
statusText: "Created"
}, ret);
}));
o.respond();
}).nThen(function () {
o.assertReqs( o.assertReqs(
3, 3,
"put -> 1 request to get csrf token, 1 to get doc and 1 to post data" "post -> 1 request to get csrf token, 1 to get doc and 1 to post data"
); );
}));
});
test("Post2", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// post but document already exists (post = error!, put = ok) // post but document already exists (post = error!, put = ok)
o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<page xmlns="http://www.xwiki.org"><title>hello there</title></page>'; '<page xmlns="http://www.xwiki.org"><title>hello there</title></page>';
o.addFakeServerResponse("xwiki", "GET", "myFile2", 200, o.answer); o.addFakeServerResponse("xwiki", "GET", "pages/myFile2", 200, o.answer);
o.spy(o, "status", 409, "Post but document already exists");
o.jio.post({"_id": "myFile2", "title": "hello again"}, o.f); o.jio.post({"_id": "myFile2", "title": "hello again"}).
o.clock.tick(5000); then(function () {
o.server.respond(); ok(0);
o.assertReqs(1, "post w/ existing doc -> 1 request to get doc then fail"); }, waitFor(function (err) {
deepEqual({
util.closeAndcleanUpJio(o.jio); error: "conflict",
id: "myFile2",
message: "Cannot create a new document",
method: "post",
reason: "document exists",
result: "error",
status: 409,
statusText: "Conflict"
}, err);
}));
o.respond();
}).nThen(function () {
o.assertReqs(2, "post w/ existing doc -> 2 request to get doc then fail");
}));
}); });
test("Put", function () { test("PutNoId", function () {
var o = setUp(this); var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// put without id => id required // put without id => id required
o.spy(o, "status", 20, "Put without id"); o.jio.put({}).then(function () {
o.jio.put({}, o.f); ok(0);
o.clock.tick(5000); }, waitFor(function (err) {
deepEqual({
"error": "bad_request",
"message": "Document id must be a non empty string.",
"method": "put",
"reason": "wrong document id",
"result": "error",
"status": 400,
"statusText": "Bad Request"
}, err);
}));
}).nThen(function () {
o.assertReqs(0, "put w/o id -> 0 requests"); o.assertReqs(0, "put w/o id -> 0 requests");
}));
});
test("Put", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// put non empty document // put non empty document
o.addFakeServerResponse("xwiki", "POST", "put1", 201, "HTML RESPONSE"); o.addFakeServerResponse("xwiki", "POST", "put1", 201, "HTML RESPONSE");
o.spy(o, "value", {"ok": true, "id": "put1"}, o.jio.put({"_id": "put1", "title": "myPut1"}).
"Create = PUT non empty document"); then(waitFor(function (res) {
o.jio.put({"_id": "put1", "title": "myPut1"}, o.f); deepEqual({
o.clock.tick(5000); "id": "put1",
o.server.respond(); "method": "put",
"result": "success",
"status": 201,
"statusText": "Created"
}, res);
}));
o.respond();
}).nThen(function () {
o.assertReqs( o.assertReqs(
3, 3,
"put normal doc -> 1 req to get doc, 1 for csrf token, 1 to post" "put normal doc -> 1 req to get doc (404), 1 for csrf token, 1 to post"
); );
}));
});
test("PutUpdateDoc", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// put but document already exists = update // put but document already exists = update
o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<page xmlns="http://www.xwiki.org"><title>mtPut1</title></page>'; '<page xmlns="http://www.xwiki.org"><title>mtPut1</title></page>';
o.addFakeServerResponse("xwiki", "GET", "put2", 200, o.answer); o.addFakeServerResponse("xwiki", "GET", "put2", 200, o.answer);
o.addFakeServerResponse("xwiki", "POST", "put2", 201, "HTML RESPONSE"); o.addFakeServerResponse("xwiki", "POST", "put2", 201, "HTML RESPONSE");
o.spy(o, "value", {"ok": true, "id": "put2"}, "Updated the document");
o.jio.put({"_id": "put2", "title": "myPut2abcdedg"}, o.f); o.jio.put({"_id": "put2", "title": "myPut2abcdedg"}).
o.clock.tick(5000); then(waitFor(function (ret) {
o.server.respond(); deepEqual({
"id": "put2",
"method": "put",
"result": "success",
"status": 204,
"statusText": "No Content"
}, ret);
}));
o.respond();
}).nThen(function () {
o.assertReqs( o.assertReqs(
3, 4,
"put update doc -> 1 req to get doc, 1 for csrf token, 1 to post" "put update doc -> 2 req to get doc, 1 for csrf token, 1 to post"
); );
util.closeAndcleanUpJio(o.jio); }));
}); });
test("PutAttachment", function () { test("PutAttachmentNoDocId", function () {
var o = setUp(this); var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// putAttachment without doc id => id required // putAttachment without doc id => id required
o.spy(o, "status", 20, "PutAttachment without doc id"); o.jio.putAttachment({}, function () {
o.jio.putAttachment({}, o.f); ok(0);
o.clock.tick(5000); }, waitFor(function (err) {
deepEqual({
"attachment": undefined,
"error": "bad_request",
"message": "Document id must be a non empty string.",
"method": "putAttachment",
"reason": "wrong document id",
"result": "error",
"status": 400,
"statusText": "Bad Request"
}, err);
}));
o.respond();
}).nThen(function () {
o.assertReqs(0, "put attach w/o doc id -> 0 requests"); o.assertReqs(0, "put attach w/o doc id -> 0 requests");
}));
});
test("PutAttachmentNoAttachId", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// putAttachment without attachment id => attachment id required // putAttachment without attachment id => attachment id required
o.spy(o, "status", 22, "PutAttachment without attachment id"); o.jio.putAttachment({"_id": "putattmt1"}, function () {
o.jio.putAttachment({"_id": "putattmt1"}, o.f); ok(0);
o.clock.tick(5000); }, waitFor(function (err) {
deepEqual({
"attachment": undefined,
"error": "bad_request",
"id": "putattmt1",
"message": "Attachment id must be a non empty string.",
"method": "putAttachment",
"reason": "wrong attachment id",
"result": "error",
"status": 400,
"statusText": "Bad Request"
}, err);
}));
o.respond();
}).nThen(function () {
o.assertReqs(0, "put attach w/o attach id -> 0 requests"); o.assertReqs(0, "put attach w/o attach id -> 0 requests");
}));
});
test("PutAttachmentUnderlyingDocumentNonexistant", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// putAttachment without underlying document => not found // putAttachment without underlying document => not found
o.addFakeServerResponse("xwiki", "GET", "putattmtx", 404, "HTML RESPONSE"); o.jio.putAttachment(
o.spy(o, "status", 404, "PutAttachment without document"); {"_id": "putattmtx", "_attachment": "putattmt2", "_data": ""},
o.jio.putAttachment({"_id": "putattmtx", "_attachment": "putattmt2"}, o.f); function () {
o.clock.tick(5000); ok(0);
o.server.respond(); },
o.assertReqs(1, "put attach w/o existing document -> 1 request to get doc"); waitFor(function (err) {
deepEqual({
// putAttachment with document without data "attachment": "putattmt2",
o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + "error": "not_found",
'<page xmlns="http://www.xwiki.org"><title>myPutAttm</title></page>'; "id": "putattmtx",
o.addFakeServerResponse("xwiki", "GET", "putattmt1", 200, o.answer); "message": "Impossible to add attachment",
o.addFakeServerResponse( "method": "putAttachment",
"xwiki", "reason": "missing",
"POST", "result": "error",
"putattmt1/putattmt2", "status": 404,
201, "statusText": "Not Found"
"HTML RESPONSE" }, err);
})
); );
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");
util.closeAndcleanUpJio(o.jio);
});
test("Get", function () { o.respond();
var o = setUp(this); }).nThen(function () {
o.assertReqs(
1,
"put attach w/o existing document -> 1 request to get doc"
);
}));
});
test("GetNonexistantDoc", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// get inexistent document // get inexistent document
o.spy(o, "status", 404, "Get non existing document"); o.jio.get({_id: "get_nonexistant_doc"}, function () {
o.jio.get("get1", o.f); ok(0);
o.clock.tick(5000); }, waitFor(function (err) {
o.server.respond(); deepEqual({
"error": "not_found",
"id": "get_nonexistant_doc",
"message": "Cannot find document",
"method": "get",
"reason": "missing",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, err);
}));
o.respond();
}).nThen(function () {
o.assertReqs(1, "try to get nonexistent doc -> 1 request"); o.assertReqs(1, "try to get nonexistent doc -> 1 request");
}));
});
// get inexistent attachment test("GetAttachInNonexistantDoc", function () {
o.spy(o, "status", 404, "Get non existing attachment"); var o = setUp(this);
o.jio.get("get1/get2", o.f); nThenTest(o, nThen(function (waitFor) {
o.clock.tick(5000); o.jio.getAttachment(
o.server.respond(); {"_id": "noSuchDoc", "_attachment": "its_attachment"},
function () {
ok(0);
},
waitFor(function (err) {
deepEqual({
"attachment": "its_attachment",
"error": "not_found",
"id": "noSuchDoc",
"message": "Cannot find document",
"method": "getAttachment",
"reason": "missing document",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, err);
})
);
o.respond();
}).nThen(function () {
o.assertReqs(1, "try to get nonexistent attach -> 1 request"); o.assertReqs(1, "try to get nonexistent attach -> 1 request");
}));
});
// get document test("GetDoc", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<page xmlns="http://www.xwiki.org"><title>some title</title></page>'; '<page xmlns="http://www.xwiki.org"><title>some title</title></page>';
o.addFakeServerResponse("xwiki", "GET", "get3", 200, o.answer); o.addFakeServerResponse("xwiki", "GET", "get_doc", 200, o.answer);
o.spy(o, "value", {"_id": "get3", "title": "some title"}, "Get document");
o.jio.get("get3", o.f); o.jio.get({_id: "get_doc"}).then(waitFor(function (ret) {
o.clock.tick(5000); deepEqual({
o.server.respond(); "data": {
o.assertReqs(1, "get document -> 1 request"); "_attachments": {},
"_id": "get_doc",
"title": "some title"
},
"id": "get_doc",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, ret);
}));
o.respond();
}).nThen(function () {
o.assertReqs(2, "get document -> 2 request");
}));
});
test("GetNonexistantAttach", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// get inexistent attachment (document exists) // get inexistent attachment (document exists)
o.spy(o, "status", 404, "Get non existing attachment (doc exists)"); o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
o.jio.get({"_id": "get3", "_attachment": "getx"}, o.f); '<page xmlns="http://www.xwiki.org"><title>some title</title></page>';
o.clock.tick(5000); o.addFakeServerResponse(
o.server.respond(); "xwiki",
o.assertReqs(1, "get nonexistant attachment -> 1 request"); "GET",
"get_nonexistant_attach",
200,
o.answer
);
o.jio.getAttachment(
{"_id": "get_nonexistant_attach", "_attachment": "nxattach"},
function () {
ok(0);
},
waitFor(function (err) {
deepEqual({
"attachment": "nxattach",
"error": "not_found",
"id": "get_nonexistant_attach",
"message": "Cannot find attachment",
"method": "getAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, err);
})
);
o.respond();
}).nThen(function () {
o.assertReqs(2, "get nonexistant attachment -> 2 request to get doc");
}));
});
test("GetAttachHappyPath", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// get attachment // get attachment
o.answer = JSON.stringify({"_id": "get4", "title": "some attachment"}); o.addFakeServerResponse(
o.addFakeServerResponse("xwiki", "GET", "get3/get4", 200, o.answer); "xwiki",
o.spy(o, "value", {"_id": "get4", "title": "some attachment"}, "GET",
"Get attachment"); "spaces/Main/pages/get_attachment$",
o.jio.get({"_id": "get3", "_attachment": "get4"}, o.f); 200,
o.clock.tick(5000); '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
o.server.respond(); '<page xmlns="http://www.xwiki.org"><title>some title</title></page>'
o.assertReqs(1, "get attachment -> 1 request"); );
o.addFakeServerResponse(
util.closeAndcleanUpJio(o.jio); "xwiki",
"GET",
"spaces/Main/pages/get_attachment/attachments",
200,
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<attachments xmlns="http://www.xwiki.org">' +
'<attachment><name>attach_name</name></attachment>' +
'</attachments>'
);
// We can't fully test this
// because sinon doesn't support HTML5 blob responses.
//o.addFakeServerResponse(
// "xwiki", "GET", "download/Main/get_attachment/attach_name", 200,
// "content", "application/octet-stream");
o.jio.getAttachment(
{"_id": "get_attachment", "_attachment": "attach_name"},
waitFor(function (ret) {
deepEqual({
"attachment": "attach_name",
"error": "err_network_error",
"id": "get_attachment",
"message": "Failed to get attachment [get_attachment/attach_name]",
"method": "getAttachment",
"reason": "Error getting data from network",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, ret);
})
);
o.respond();
}).nThen(function () {
o.assertReqs(3, "get attachment -> 2 requests to get doc, 1 for attach");
}));
}); });
test("Remove", function () {
test("RemoveNonexistantDocument", function () {
var o = setUp(this); var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// remove inexistent document // remove inexistent document
o.addFakeServerResponse("xwiki", "GET", "remove1", 404, "HTML RESPONSE"); 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.jio.remove({"_id": "remove1"}, waitFor(function (ret) {
o.clock.tick(5000); deepEqual({
o.server.respond(); "error": "error",
"id": "remove1",
"message": "Failed to delete document [remove1]",
"method": "remove",
"reason": "Not Found",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, ret);
}));
o.respond();
}).nThen(function () {
o.assertReqs( o.assertReqs(
2, 2,
"remove nonexistent doc -> 1 request for csrf and 1 for doc" "remove nonexistent doc -> 1 request for csrf and 1 for doc"
); );
}));
});
test("RemoveNonexistantAttachment", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// remove inexistent document/attachment // remove inexistent document/attachment
o.addFakeServerResponse("xwiki", "GET", "remove1/remove2", 404, "HTML" + o.addFakeServerResponse("xwiki", "GET", "remove1/remove2", 404, "HTML" +
"RESPONSE"); "RESPONSE");
o.spy(o, "status", 404, "Remove inexistent document/attachment");
o.jio.removeAttachment({"_id": "remove1", "_attachment": "remove2"}, o.f); o.jio.removeAttachment(
o.clock.tick(5000); {"_id": "remove1", "_attachment": "remove2"},
o.server.respond(); function () {
o.assertReqs( ok(0);
2, },
"remove nonexistant attach -> 1 request for csrf and 1 for doc" waitFor(function (err) {
deepEqual({
"attachment": "remove2",
"error": "not_found",
"id": "remove1",
"message": "Document not found",
"method": "removeAttachment",
"reason": "missing document",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, err);
})
); );
// remove document o.respond();
//o.answer = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
// '<page xmlns="http://www.xwiki.org"><title>some doc</title></page>'; }).nThen(function () {
//o.addFakeServerResponse("xwiki", "GET", "remove3", 200, o.answer); o.assertReqs(1, "remove nonexistant attach -> 1 request for doc");
o.addFakeServerResponse("xwiki", "POST", "bin/delete/Main/remove3", }));
});
test("RemoveDocument", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
o.addFakeServerResponse("xwiki", "POST", "delete/Main/remove3",
200, "HTML RESPONSE"); 200, "HTML RESPONSE");
o.spy(o, "value", {"ok": true, "id": "remove3"}, "Remove document");
o.jio.remove({"_id": "remove3"}, o.f); o.jio.remove({"_id": "remove3"}).then(waitFor(function (ret) {
o.clock.tick(5000); deepEqual({
o.server.respond(); "id": "remove3",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, ret);
}));
o.respond();
}).nThen(function () {
o.assertReqs( o.assertReqs(
2, 2,
"remove document -> 1 request for csrf and 1 for deleting doc" "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"
}
}
}); });
test("RemoveAttachment", function () {
var o = setUp(this);
nThenTest(o, nThen(function (waitFor) {
// remove attachment // remove attachment
o.addFakeServerResponse(
"xwiki",
"GET",
"spaces/Main/pages/remove4$",
200,
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<page xmlns="http://www.xwiki.org"><title>some title</title></page>'
);
o.addFakeServerResponse(
"xwiki",
"GET",
"spaces/Main/pages/remove4/attachments",
200,
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<attachments xmlns="http://www.xwiki.org">' +
'<attachment><name>remove5</name></attachment>' +
'</attachments>'
);
o.addFakeServerResponse( o.addFakeServerResponse(
"xwiki", "xwiki",
"POST", "POST",
...@@ -298,14 +717,28 @@ ...@@ -298,14 +717,28 @@
200, 200,
"HTML RESPONSE" "HTML RESPONSE"
); );
o.spy(o, "value", {"ok": true, "id": "remove4/remove5"},
"Remove attachment"); o.jio.removeAttachment(
o.jio.removeAttachment({"_id": "remove4", "_attachment": "remove5"}, o.f); {"_id": "remove4", "_attachment": "remove5"}
o.clock.tick(5000); ).always(waitFor(function (ret) {
o.server.respond(); deepEqual({
o.assertReqs(2, "remove attach -> 1 request for csrf and 1 for deletion"); "attachment": "remove5",
"id": "remove4",
util.closeAndcleanUpJio(o.jio); "method": "removeAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, ret);
}));
o.respond();
}).nThen(function () {
o.assertReqs(
4,
"remove attach -> get doc, get attachments, get csrf, remove attach"
);
}));
}); });
})); }));
...@@ -32,6 +32,10 @@ ...@@ -32,6 +32,10 @@
<script src="queries/key-localstorage.tests.js"></script> <script src="queries/key-localstorage.tests.js"></script>
<script src="jio.storage/localstorage.tests.js"></script> <script src="jio.storage/localstorage.tests.js"></script>
<script src="../lib/jquery/jquery.min.js"></script>
<script src="../src/jio.storage/xwikistorage.js"></script>
<script src="jio.storage/xwikistorage.tests.js"></script>
<script src="../src/jio.storage/davstorage.js"></script> <script src="../src/jio.storage/davstorage.js"></script>
<script src="jio.storage/davstorage.tests.js"></script> <script src="jio.storage/davstorage.tests.js"></script>
......
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