Commit fa7a5f07 authored by Tristan Cavelier's avatar Tristan Cavelier

replicaterevisionstorage.js post command added + tests

parent 9268d81a
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO: true */
/**
* JIO Replicate Revision Storage.
* It manages storages that manage revisions and conflicts.
* Description:
* {
* "type": "replicaterevision",
* "storage_list": [
* <sub storage description>,
* ...
* ]
* }
*/
jIO.addStorageType('replicaterevision', function (spec, my) {
"use strict";
var that, priv = {};
spec = spec || {};
that = my.basicStorage(spec, my);
priv.storage_list_key = "storage_list";
priv.storage_list = spec[priv.storage_list_key];
my.env = my.env || spec.env || {};
that.specToStore = function () {
var o = {};
o[priv.storage_list_key] = priv.storage_list;
o.env = my.env;
return o;
};
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
priv.generateUuid = function () {
var S4 = function () {
var i, string = Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = "0" + string;
}
return string;
};
return S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
};
/**
* Generates a hash code of a string
* @method hashCode
* @return {string} The next revision
*/
priv.getNextRevision = function (docid) {
my.env[docid].id += 1;
return my.env[docid].id.toString();
};
/**
* Checks a revision format
* @method checkRevisionFormat
* @param {string} revision The revision string
* @return {boolean} True if ok, else false
*/
priv.checkRevisionFormat = function (revision) {
return (/^[0-9a-zA-Z_]+$/.test(revision));
};
/**
* Initalize document environment object
* @method initEnv
* @param {string} docid The document id
* @return {object} The reference to the environment
*/
priv.initEnv = function (docid) {
my.env[docid] = {
"id": 0,
"distant_revisions": {},
"my_revisions": {},
"last_revisions": []
};
return my.env[docid];
};
/**
* Post the document metadata to all sub storages
* @method post
* @param {object} command The JIO command
*/
that.post = function (command) {
var functions = {}, doc_env, revs_info, doc, my_rev;
functions.begin = function () {
doc = command.cloneDoc();
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"^[0-9]+-[0-9a-zA-Z]+$",
"reason": "Previous revision is wrong"
});
return;
}
if (typeof doc._id !== "string") {
doc._id = priv.generateUuid();
}
if (priv.update_doctree_allowed === undefined) {
priv.update_doctree_allowed = true;
}
doc_env = my.env[doc._id];
if (doc_env && doc_env.id) {
if (!priv.update_doctree_allowed) {
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Cannot update a document",
"reason": "Document update conflict"
});
return;
}
} else {
doc_env = priv.initEnv(doc._id);
}
my_rev = priv.getNextRevision(doc._id);
functions.sendDocument();
};
functions.sendDocumentIndex = function (method, index, callback) {
var wrapped_callback_success, wrapped_callback_error;
wrapped_callback_success = function (response) {
callback(method, index, undefined, response);
};
wrapped_callback_error = function (err) {
callback(method, index, err, undefined);
};
if (typeof doc._rev === "string" &&
doc_env.my_revisions[doc._rev] !== undefined) {
doc._rev = doc_env.my_revisions[doc._rev][index];
}
that.addJob(
method,
priv.storage_list[index],
doc,
command.cloneOption(),
wrapped_callback_success,
wrapped_callback_error
);
};
functions.sendDocument = function () {
var i;
doc_env.my_revisions[my_rev] = doc_env.my_revisions[my_rev] || [];
doc_env.my_revisions[my_rev].length = priv.storage_list.length;
for (i = 0; i < priv.storage_list.length; i += 1) {
functions.sendDocumentIndex(
doc_env.last_revisions[i] === "unique_" + i ? "put" : "post",
i,
functions.checkSendResult
);
}
};
functions.checkSendResult = function (method, index, err, response) {
if (err) {
if (err.status === 409) {
if (method !== "put") {
functions.sendDocumentIndex(
"put",
index,
functions.checkSendResult
);
return;
}
}
functions.updateEnv(index, undefined);
functions.error(err);
return;
}
// success
functions.updateEnv(index, response.rev || "unique_" + index);
functions.success({"ok": true, "id": doc._id, "rev": my_rev});
};
functions.updateEnv = function (index, revision) {
doc_env.last_revisions[index] = revision;
doc_env.my_revisions[my_rev][index] = revision;
doc_env.distant_revisions[revision] = my_rev;
};
functions.success = function (response) {
if (!functions.success_called_once) {
functions.success_called_once = true;
that.success(response);
}
};
functions.error_count = 0;
functions.error = function (err) {
functions.error_count += 1;
if (functions.error_count === priv.storage_list.length) {
that.error(err);
}
};
functions.begin();
};
return that;
});
...@@ -1813,6 +1813,200 @@ test ("Scenario", function(){ ...@@ -1813,6 +1813,200 @@ test ("Scenario", function(){
}); });
module ("JIO Replicate Revision Storage");
var testReplicateRevisionStorageGenerator = function (
sinon, jio_description, document_name_have_revision
) {
var o = generateTools(sinon), leavesAction, generateLocalPath;
o.jio = JIO.newJio(jio_description);
generateLocalPath = function (storage_description) {
return "jio/localstorage/" + storage_description.username + "/" +
storage_description.application_name;
};
leavesAction = function (action, storage_description, param) {
var i;
if (param === undefined) {
param = {};
} else {
param = clone(param);
}
if (storage_description.storage_list !== undefined) {
// it is the replicate revision storage tree
for (i = 0; i < storage_description.storage_list.length; i += 1) {
leavesAction(action, storage_description.storage_list[i], param);
}
} else if (storage_description.sub_storage !== undefined) {
// it is the revision storage tree
param.revision = true;
leavesAction(action, storage_description.sub_storage, param);
} else {
// it is the storage tree leaf
param[storage_description.type] = true;
action(storage_description, param);
}
};
o.leavesAction = function (action) {
leavesAction(action, jio_description);
};
// post a new document without id
o.doc = {"title": "post document without id"};
o.revision = {"start": 0, "ids": []};
o.spy(o, "status", undefined, "Post document (without id)");
o.jio.post(o.doc, function (err, response) {
o.f.apply(arguments);
o.response_rev = (err || response).rev;
if (isUuid((err || response).id)) {
ok(true, "Uuid format");
o.uuid = (err || response).id;
} else {
deepEqual((err || response).id,
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "Uuid format");
}
});
o.tick(o);
// check document
o.doc._id = o.uuid;
o.rev = "1";
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision);
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
if (param.revision) {
deepEqual(o.response_rev, o.rev, "Check revision");
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
"/" + o.uuid + suffix),
doc, "Check document"
);
});
// post a new document with id
o.doc = {"_id": "post1", "title": "post new doc with id"};
o.rev = "1"
o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev},
"Post document (with id)");
o.jio.post(o.doc, o.f);
o.tick(o);
// check document
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision);
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
if (param.revision) {
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
"/post1" + suffix),
doc, "Check document"
);
});
// post same document without revision
o.doc = {"_id": "post1", "title": "post same document without revision"};
o.rev = "2";
o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev},
"Post same document (without revision)");
o.jio.post(o.doc, o.f);
o.tick(o);
// check document
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision);
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
if (param.revision) {
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
"/post1" + suffix),
doc, "Check document"
);
});
// post a new revision
o.doc = {"_id": "post1", "title": "post new revision", "_rev": o.rev};
o.rev = "3";
o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev},
"Post document (with revision)");
o.jio.post(o.doc, o.f);
o.tick(o);
// check document
o.revision.start += 1;
o.revision.ids.unshift(o.local_rev.split("-").slice(1).join("-"));
o.doc._rev = o.local_rev;
o.local_rev = "2-" + generateRevisionHash(o.doc, o.revision);
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
delete doc._rev;
if (param.revision) {
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
"/post1" + suffix),
doc, "Check document"
);
});
o.jio.stop();
};
test ("[Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "local",
"username": "ureploc",
"application_name": "areploc"
}]
});
});
test ("[Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
"sub_storage": {
"type": "local",
"username": "ureprevloc",
"application_name": "areprevloc"
}
}]
});
});
test ("[Revision + Local Storage, Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
"sub_storage": {
"type": "local",
"username": "ureprevlocloc",
"application_name": "areprevlocloc"
}
},{
"type": "local",
"username": "ureprevlocloc2",
"application_name": "areprevlocloc2"
}]
});
});
/* /*
module ('Jio DAVStorage'); module ('Jio DAVStorage');
......
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