/*jslint indent: 2, maxlen: 80, nomen: true */ /*global define, RSVP, jIO, fake_storage, module, test, stop, start, deepEqual, setTimeout, clearTimeout, XMLHttpRequest, window */ (function (dependencies, factory) { "use strict"; if (typeof define === 'function' && define.amd) { return define(dependencies, factory); } factory(RSVP, jIO, fake_storage); }([ "rsvp", "jio", "fakestorage", "replicatestorage" ], function (RSVP, jIO, fake_storage) { "use strict"; var all = RSVP.all, chain = RSVP.resolve, Promise = RSVP.Promise; /** * sleep(delay, [value]): promise< value > * * Produces a new promise which will resolve with `value` after `delay` * milliseconds. * * @param {Number} delay The time to sleep. * @param {Any} [value] The value to resolve. * @return {Promise} A new promise. */ function sleep(delay, value) { var ident; return new Promise(function (resolve) { ident = setTimeout(resolve, delay, value); }, function () { clearTimeout(ident); }); } function jsonClone(object, replacer) { if (object === undefined) { return undefined; } return JSON.parse(JSON.stringify(object, replacer)); } function reverse(promise) { return promise.then(function (a) { throw a; }, function (e) { return e; }); } function orderRowsById(a, b) { return a.id > b.id ? 1 : b.id > a.id ? -1 : 0; } module("Replicate + GID + Local"); test("Get", function () { var shared = {}, i, jio_list, replicate_jio; // this test can work with at least 2 sub storages shared.gid_description = { "type": "gid", "constraints": { "default": { "identifier": "list" } }, "sub_storage": null }; shared.storage_description_list = []; for (i = 0; i < 4; i += 1) { shared.storage_description_list[i] = jsonClone(shared.gid_description); shared.storage_description_list[i].sub_storage = { "type": "local", "username": "replicate scenario test for get method - " + (i + 1), "mode": "memory" }; } shared.replicate_storage_description = { "type": "replicate", "storage_list": shared.storage_description_list }; shared.workspace = {}; shared.jio_option = { "workspace": shared.workspace, "max_retry": 0 }; jio_list = shared.storage_description_list.map(function (description) { return jIO.createJIO(description, shared.jio_option); }); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); stop(); shared.modified_date_list = [ new Date("1995"), new Date("2000"), null, new Date("Invalid Date") ]; shared.winner_modified_date = shared.modified_date_list[1]; function setFakeStorage() { setFakeStorage.original = shared.storage_description_list[0].sub_storage; shared.storage_description_list[0].sub_storage = { "type": "fake", "id": "replicate scenario test for get method - 1" }; jio_list[0] = jIO.createJIO( shared.storage_description_list[0], shared.jio_option ); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); } function unsetFakeStorage() { shared.storage_description_list[0].sub_storage = setFakeStorage.original; jio_list[0] = jIO.createJIO( shared.storage_description_list[0], shared.jio_option ); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); } function putSimilarDocuments() { return all(jio_list.map(function (jio) { return jio.post({ "identifier": "a", "modified": shared.modified_date_list[0] }); })); } function getDocumentNothingToSynchronize() { return replicate_jio.get({"_id": "{\"identifier\":[\"a\"]}"}); } function getDocumentNothingToSynchronizeTest(answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"a\"]}", "identifier": "a", "modified": shared.modified_date_list[0].toJSON() }, "id": "{\"identifier\":[\"a\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Get document, nothing to synchronize."); // check storage state return sleep(500). // possible synchronization in background (should not occur) then(function () { return all(jio_list.map(function (jio) { return jio.get({"_id": "{\"identifier\":[\"a\"]}"}); })); }).then(function (answers) { answers.forEach(function (answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"a\"]}", "identifier": "a", "modified": shared.modified_date_list[0].toJSON() }, "id": "{\"identifier\":[\"a\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); }); }); } function putDifferentDocuments() { return all(jio_list.map(function (jio, i) { return jio.post({ "identifier": "b", "modified": shared.modified_date_list[i] }); })); } function getDocumentWithSynchronization() { return replicate_jio.get({"_id": "{\"identifier\":[\"b\"]}"}); } function getDocumentWithSynchronizationTest(answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"b\"]}", "identifier": "b", "modified": shared.modified_date_list[0].toJSON() }, "id": "{\"identifier\":[\"b\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Get document, pending synchronization."); // check storage state return sleep(500). // synchronizing in background then(function () { return all(jio_list.map(function (jio) { return jio.get({"_id": "{\"identifier\":[\"b\"]}"}); })); }).then(function (answers) { answers.forEach(function (answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"b\"]}", "identifier": "b", "modified": shared.winner_modified_date.toJSON() }, "id": "{\"identifier\":[\"b\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); }); }); } function putOneDocument() { return jio_list[1].post({ "identifier": "c", "modified": shared.modified_date_list[1] }); } function getDocumentWith404Synchronization() { return replicate_jio.get({"_id": "{\"identifier\":[\"c\"]}"}); } function getDocumentWith404SynchronizationTest(answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"c\"]}", "identifier": "c", "modified": shared.modified_date_list[1].toJSON() }, "id": "{\"identifier\":[\"c\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Get document, synchronizing with not found document."); // check storage state return sleep(500). // synchronizing in background then(function () { return all(jio_list.map(function (jio) { return jio.get({"_id": "{\"identifier\":[\"c\"]}"}); })); }).then(function (answers) { answers.forEach(function (answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"c\"]}", "identifier": "c", "modified": shared.winner_modified_date.toJSON() }, "id": "{\"identifier\":[\"c\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); }); }); } function putDifferentDocuments2() { return all(jio_list.map(function (jio, i) { return jio.post({ "identifier": "d", "modified": shared.modified_date_list[i] }); })); } function getDocumentWithUnavailableStorage() { setFakeStorage(); setTimeout(function () { fake_storage.commands[ "replicate scenario test for get method - 1/allDocs" ].error({"status": 0}); }, 100); return replicate_jio.get({"_id": "{\"identifier\":[\"d\"]}"}); } function getDocumentWithUnavailableStorageTest(answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"d\"]}", "identifier": "d", "modified": shared.modified_date_list[1].toJSON() }, "id": "{\"identifier\":[\"d\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Get document, synchronizing with unavailable storage."); unsetFakeStorage(); // check storage state return sleep(500). // synchronizing in background then(function () { return all(jio_list.map(function (jio) { return jio.get({"_id": "{\"identifier\":[\"d\"]}"}); })); }).then(function (answers) { deepEqual(answers[0], { "data": { "_id": "{\"identifier\":[\"d\"]}", "identifier": "d", "modified": shared.modified_date_list[0].toJSON() }, "id": "{\"identifier\":[\"d\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); answers.slice(1).forEach(function (answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"d\"]}", "identifier": "d", "modified": shared.winner_modified_date.toJSON() }, "id": "{\"identifier\":[\"d\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); }); }); } function unexpectedError(error) { if (error instanceof Error) { deepEqual([ error.name + ": " + error.message, error ], "NO ERROR", "Unexpected error"); } else { deepEqual(error, "NO ERROR", "Unexpected error"); } } chain(). // get without synchronizing anything then(putSimilarDocuments). then(getDocumentNothingToSynchronize). then(getDocumentNothingToSynchronizeTest). // get with synchronization then(putDifferentDocuments). then(getDocumentWithSynchronization). then(getDocumentWithSynchronizationTest). // get with 404 synchronization then(putOneDocument). then(getDocumentWith404Synchronization). then(getDocumentWith404SynchronizationTest). // XXX get with attachment synchronization // get with unavailable storage then(putDifferentDocuments2). then(getDocumentWithUnavailableStorage). then(getDocumentWithUnavailableStorageTest). // End of scenario then(null, unexpectedError). then(start, start); }); test("Post + Put", function () { var shared = {}, i, jio_list, replicate_jio; // this test can work with at least 2 sub storages shared.gid_description = { "type": "gid", "constraints": { "default": { "identifier": "list" } }, "sub_storage": null }; shared.storage_description_list = []; for (i = 0; i < 4; i += 1) { shared.storage_description_list[i] = jsonClone(shared.gid_description); shared.storage_description_list[i].sub_storage = { "type": "local", "username": "replicate scenario test for post method - " + (i + 1), "mode": "memory" }; } shared.replicate_storage_description = { "type": "replicate", "storage_list": shared.storage_description_list }; shared.workspace = {}; shared.jio_option = { "workspace": shared.workspace, "max_retry": 0 }; jio_list = shared.storage_description_list.map(function (description) { return jIO.createJIO(description, shared.jio_option); }); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); stop(); function setFakeStorage() { setFakeStorage.original = shared.storage_description_list[0].sub_storage; shared.storage_description_list[0].sub_storage = { "type": "fake", "id": "replicate scenario test for post method - 1" }; jio_list[0] = jIO.createJIO( shared.storage_description_list[0], shared.jio_option ); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); } function unsetFakeStorage() { shared.storage_description_list[0].sub_storage = setFakeStorage.original; jio_list[0] = jIO.createJIO( shared.storage_description_list[0], shared.jio_option ); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); } function createDocument() { return replicate_jio.post({"identifier": "a"}); } function createDocumentTest(answer) { deepEqual(answer, { "id": "{\"identifier\":[\"a\"]}", "method": "post", "result": "success", "status": 201, "statusText": "Created" }, "Post document"); } function checkStorageContent() { // check storage state return all(jio_list.map(function (jio) { return jio.get({"_id": "{\"identifier\":[\"a\"]}"}); })).then(function (answers) { answers.forEach(function (answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"a\"]}", "identifier": "a" }, "id": "{\"identifier\":[\"a\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); }); }); } function updateDocument() { return replicate_jio.put({ "_id": "{\"identifier\":[\"a\"]}", "identifier": "a", "title": "b" }); } function updateDocumentTest(answer) { deepEqual(answer, { "id": "{\"identifier\":[\"a\"]}", "method": "put", "result": "success", "status": 204, "statusText": "No Content" }, "Update document"); } function checkStorageContent3() { // check storage state return all(jio_list.map(function (jio) { return jio.get({"_id": "{\"identifier\":[\"a\"]}"}); })).then(function (answers) { answers.forEach(function (answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"a\"]}", "identifier": "a", "title": "b" }, "id": "{\"identifier\":[\"a\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); }); }); } function createDocumentWithUnavailableStorage() { setFakeStorage(); setTimeout(function () { fake_storage.commands[ "replicate scenario test for post method - 1/allDocs" ].error({"status": 0}); }, 100); return replicate_jio.post({"identifier": "b"}); } function createDocumentWithUnavailableStorageTest(answer) { deepEqual(answer, { "id": "{\"identifier\":[\"b\"]}", "method": "post", "result": "success", "status": 201, "statusText": "Created" }, "Post document with unavailable storage"); return sleep(100); } function checkStorageContent2() { unsetFakeStorage(); // check storage state return all(jio_list.map(function (jio, i) { if (i === 0) { return reverse(jio.get({"_id": "{\"identifier\":[\"b\"]}"})); } return jio.get({"_id": "{\"identifier\":[\"b\"]}"}); })).then(function (answers) { deepEqual(answers[0], { "error": "not_found", "id": "{\"identifier\":[\"b\"]}", "message": "Cannot get document", "method": "get", "reason": "missing", "result": "error", "status": 404, "statusText": "Not Found" }, "Check storage content"); answers.slice(1).forEach(function (answer) { deepEqual(answer, { "data": { "_id": "{\"identifier\":[\"b\"]}", "identifier": "b" }, "id": "{\"identifier\":[\"b\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); }); }); } function unexpectedError(error) { if (error instanceof Error) { deepEqual([ error.name + ": " + error.message, error ], "NO ERROR", "Unexpected error"); } else { deepEqual(error, "NO ERROR", "Unexpected error"); } } chain(). // create a document then(createDocument). then(createDocumentTest). then(checkStorageContent). // update document then(updateDocument). then(updateDocumentTest). then(checkStorageContent3). // create a document with unavailable storage then(createDocumentWithUnavailableStorage). then(createDocumentWithUnavailableStorageTest). then(checkStorageContent2). // End of scenario then(null, unexpectedError). then(start, start); }); test("Remove", function () { var shared = {}, i, jio_list, replicate_jio; // this test can work with at least 2 sub storages shared.gid_description = { "type": "gid", "constraints": { "default": { "identifier": "list" } }, "sub_storage": null }; shared.storage_description_list = []; for (i = 0; i < 4; i += 1) { shared.storage_description_list[i] = jsonClone(shared.gid_description); shared.storage_description_list[i].sub_storage = { "type": "local", "username": "replicate scenario test for remove method - " + (i + 1), "mode": "memory" }; } shared.replicate_storage_description = { "type": "replicate", "storage_list": shared.storage_description_list }; shared.workspace = {}; shared.jio_option = { "workspace": shared.workspace, "max_retry": 0 }; jio_list = shared.storage_description_list.map(function (description) { return jIO.createJIO(description, shared.jio_option); }); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); stop(); function setFakeStorage() { setFakeStorage.original = shared.storage_description_list[0].sub_storage; shared.storage_description_list[0].sub_storage = { "type": "fake", "id": "replicate scenario test for remove method - 1" }; jio_list[0] = jIO.createJIO( shared.storage_description_list[0], shared.jio_option ); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); } function unsetFakeStorage() { shared.storage_description_list[0].sub_storage = setFakeStorage.original; jio_list[0] = jIO.createJIO( shared.storage_description_list[0], shared.jio_option ); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); } function putSomeDocuments() { return all(jio_list.map(function (jio) { return jio.post({"identifier": "a"}); })); } function removeDocument() { return replicate_jio.remove({"_id": "{\"identifier\":[\"a\"]}"}); } function removeDocumentTest(answer) { deepEqual(answer, { "id": "{\"identifier\":[\"a\"]}", "method": "remove", "result": "success", "status": 204, "statusText": "No Content" }, "Remove document"); } function checkStorageContent() { // check storage state return all(jio_list.map(function (jio) { return reverse(jio.get({"_id": "{\"identifier\":[\"a\"]}"})); })).then(function (answers) { answers.forEach(function (answer) { deepEqual(answer, { "error": "not_found", "id": "{\"identifier\":[\"a\"]}", "message": "Cannot get document", "method": "get", "reason": "missing", "result": "error", "status": 404, "statusText": "Not Found" }, "Check storage content"); }); }); } function putSomeDocuments2() { return all(jio_list.map(function (jio) { return jio.post({"identifier": "b"}); })); } function removeDocumentWithUnavailableStorage() { setFakeStorage(); setTimeout(function () { fake_storage.commands[ "replicate scenario test for remove method - 1/allDocs" ].error({"status": 0}); }, 100); return replicate_jio.remove({"_id": "{\"identifier\":[\"b\"]}"}); } function removeDocumentWithUnavailableStorageTest(answer) { deepEqual(answer, { "id": "{\"identifier\":[\"b\"]}", "method": "remove", "result": "success", "status": 204, "statusText": "No Content" }, "Remove document with unavailable storage"); return sleep(100); } function checkStorageContent2() { unsetFakeStorage(); // check storage state return all(jio_list.map(function (jio, i) { if (i === 0) { return jio.get({"_id": "{\"identifier\":[\"b\"]}"}); } return reverse(jio.get({"_id": "{\"identifier\":[\"b\"]}"})); })).then(function (answers) { deepEqual(answers[0], { "data": { "_id": "{\"identifier\":[\"b\"]}", "identifier": "b" }, "id": "{\"identifier\":[\"b\"]}", "method": "get", "result": "success", "status": 200, "statusText": "Ok" }, "Check storage content"); answers.slice(1).forEach(function (answer) { deepEqual(answer, { "error": "not_found", "id": "{\"identifier\":[\"b\"]}", "message": "Cannot get document", "method": "get", "reason": "missing", "result": "error", "status": 404, "statusText": "Not Found" }, "Check storage content"); }); }); } function unexpectedError(error) { if (error instanceof Error) { deepEqual([ error.name + ": " + error.message, error ], "NO ERROR", "Unexpected error"); } else { deepEqual(error, "NO ERROR", "Unexpected error"); } } chain(). // remove document then(putSomeDocuments). then(removeDocument). then(removeDocumentTest). then(checkStorageContent). // remove document with unavailable storage then(putSomeDocuments2). then(removeDocumentWithUnavailableStorage). then(removeDocumentWithUnavailableStorageTest). then(checkStorageContent2). // End of scenario then(null, unexpectedError). then(start, start); }); test("AllDocs", function () { var shared = {}, i, jio_list, replicate_jio; // this test can work with at least 2 sub storages shared.gid_description = { "type": "gid", "constraints": { "default": { "identifier": "list" } }, "sub_storage": null }; shared.storage_description_list = []; for (i = 0; i < 2; i += 1) { shared.storage_description_list[i] = jsonClone(shared.gid_description); shared.storage_description_list[i].sub_storage = { "type": "local", "username": "replicate scenario test for allDocs method - " + (i + 1), "mode": "memory" }; } shared.replicate_storage_description = { "type": "replicate", "storage_list": shared.storage_description_list }; shared.workspace = {}; shared.jio_option = { "workspace": shared.workspace, "max_retry": 0 }; jio_list = shared.storage_description_list.map(function (description) { return jIO.createJIO(description, shared.jio_option); }); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); stop(); shared.modified_date_list = [ new Date("2000"), new Date("1995"), null, new Date("Invalid Date") ]; function postSomeDocuments() { return all([ jio_list[0].post({ "identifier": "a", "modified": shared.modified_date_list[0] }), jio_list[0].post({ "identifier": "b", "modified": shared.modified_date_list[1] }), jio_list[1].post({ "identifier": "b", "modified": shared.modified_date_list[0] }) ]); } function listDocuments() { return replicate_jio.allDocs({"include_docs": true}); } function listDocumentsTest(answer) { answer.data.rows.sort(orderRowsById); deepEqual(answer, { "data": { "total_rows": 2, "rows": [ { "id": "{\"identifier\":[\"a\"]}", "doc": { "_id": "{\"identifier\":[\"a\"]}", "identifier": "a", "modified": shared.modified_date_list[0].toJSON() }, "value": {} }, { "id": "{\"identifier\":[\"b\"]}", "doc": { "_id": "{\"identifier\":[\"b\"]}", "identifier": "b", "modified": shared.modified_date_list[1].toJSON() // there's no winner detection here }, "value": {} } ] }, "method": "allDocs", "result": "success", "status": 200, "statusText": "Ok" }, "Document list should be merged correctly"); } function setFakeStorage() { shared.storage_description_list[0].sub_storage = { "type": "fake", "id": "replicate scenario test for allDocs method - 1" }; jio_list[0] = jIO.createJIO( shared.storage_description_list[0], shared.jio_option ); replicate_jio = jIO.createJIO( shared.replicate_storage_description, shared.jio_option ); } function listDocumentsWithUnavailableStorage() { setTimeout(function () { fake_storage.commands[ "replicate scenario test for allDocs method - 1/allDocs" ].error({"status": 0}); }, 100); return replicate_jio.allDocs({"include_docs": true}); } function listDocumentsWithUnavailableStorageTest(answer) { deepEqual(answer, { "data": { "total_rows": 1, "rows": [ { "id": "{\"identifier\":[\"b\"]}", "doc": { "_id": "{\"identifier\":[\"b\"]}", "identifier": "b", "modified": shared.modified_date_list[0].toJSON() }, "value": {} } ] }, "method": "allDocs", "result": "success", "status": 200, "statusText": "Ok" }, "Document list with only one available storage"); } function unexpectedError(error) { if (error instanceof Error) { deepEqual([ error.name + ": " + error.message, error ], "NO ERROR", "Unexpected error"); } else { deepEqual(error, "NO ERROR", "Unexpected error"); } } chain(). // list documents then(postSomeDocuments). then(listDocuments). then(listDocumentsTest). // set fake storage then(setFakeStorage). // list documents with unavailable storage then(listDocumentsWithUnavailableStorage). then(listDocumentsWithUnavailableStorageTest). // End of scenario then(null, unexpectedError). then(start); }); }));