diff --git a/Makefile b/Makefile index 4effbf04cbd2709aaaa15b42571ee828c4e6ed6c..90af380a12a464c1b04d47046c7f91b0215882c6 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,11 @@ ${LINTDIR}/queries/query.js: ${SRCDIR}/queries/query.js ${JSLINT} ${LINTOPTS} $< @cat $< > $@ +${LINTDIR}/indexeddb/indexeddb.js: ${SRCDIR}/indexeddb/indexeddb.js + @mkdir -p $(@D) + ${JSLINT} ${LINTOPTS} $< + @cat $< > $@ + ${LINTDIR}/${TESTDIR}/jio.storage/%.js: ${TESTDIR}/jio.storage/%.js @mkdir -p $(@D) ${JSLINT} ${LINTOPTS} --predef QUnit --predef RSVP --predef jIO $< @@ -104,6 +109,7 @@ lint: $(patsubst ${TESTDIR}/jio.storage/%.js, ${LINTDIR}/${TESTDIR}/jio.storage/ $(patsubst ${EXAMPLEDIR}/%.js, ${LINTDIR}/${EXAMPLEDIR}/%.js, $(wildcard ${EXAMPLEDIR}/*.js)) \ ${LINTDIR}/queries/query.js \ ${LINTDIR}/jio.date/jiodate.js \ + ${LINTDIR}/indexeddb/indexeddb.js \ ${LINTDIR}/jio.js \ ${LINTDIR}/node/jio.js \ ${LINTDIR}/${TESTDIR}/node.js \ @@ -129,6 +135,7 @@ ${JIOVERSION}: ${EXTERNALDIR}/URI.js \ ${SRCDIR}/queries/parser-end.js \ ${SRCDIR}/queries/query.js \ ${SRCDIR}/jio.date/jiodate.js \ + ${SRCDIR}/indexeddb/indexeddb.js \ ${SRCDIR}/jio.js \ ${EXTERNALDIR}/rusha.js \ ${SRCDIR}/jio.storage/replicatestorage.js \ @@ -151,7 +158,10 @@ ${JIOVERSION}: ${EXTERNALDIR}/URI.js \ ${SRCDIR}/jio.storage/indexeddbstorage.js \ ${SRCDIR}/jio.storage/cryptstorage.js \ ${SRCDIR}/jio.storage/fbstorage.js \ - ${SRCDIR}/jio.storage/cloudooostorage.js + ${SRCDIR}/jio.storage/cloudooostorage.js \ + ${SRCDIR}/jio.storage/nocapacitystorage.js \ + ${SRCDIR}/jio.storage/liststorage.js \ + ${SRCDIR}/jio.storage/indexstorage2.js @mkdir -p $(@D) cat $^ > $@ diff --git a/examples/scenario.js b/examples/scenario.js index a196e1362fbbc86beb1771634fb16e485a1de1d8..879f373c68e45ccb62d9a6c3056fab5b1e06cc50 100644 --- a/examples/scenario.js +++ b/examples/scenario.js @@ -18,7 +18,7 @@ * See https://www.nexedi.com/licensing for rationale and options. */ -/*global console, btoa, Blob*/ +/*global console, btoa, Blob, indexedDB*/ /*jslint nomen: true, maxlen: 200*/ (function (window, QUnit, jIO, rJS) { "use strict"; @@ -28,7 +28,9 @@ ok = QUnit.ok, stop = QUnit.stop, start = QUnit.start, - deepEqual = QUnit.deepEqual; + deepEqual = QUnit.deepEqual, + test_signature_database = 'test_signature_storage_scenario'; + rJS(window) @@ -60,12 +62,22 @@ return g.run({ type: "query", sub_storage: { - type: "uuid", + type: "list", + signature_storage: { + type: "indexeddb", + database: test_signature_database + }, sub_storage: { - type: "union", - storage_list: [{ - type: "memory" - }] + type: "nocapacity", + sub_storage: { + type: "uuid", + sub_storage: { + type: "union", + storage_list: [{ + type: "memory" + }] + } + } } } }); @@ -362,6 +374,9 @@ .then(function () { return jio.repair(); }) + .then(function () { + return indexedDB.deleteDatabase('jio:' + test_signature_database); + }) .fail(function (error) { console.error("---"); diff --git a/src/indexeddb/indexeddb.js b/src/indexeddb/indexeddb.js new file mode 100644 index 0000000000000000000000000000000000000000..ef56d17c2d220b0ab554707f79323b05cce6587f --- /dev/null +++ b/src/indexeddb/indexeddb.js @@ -0,0 +1,176 @@ +/* + * Copyright 2019, Nexedi SA + * + * This program is free software: you can Use, Study, Modify and Redistribute + * it under the terms of the GNU General Public License version 3, or (at your + * option) any later version, as published by the Free Software Foundation. + * + * You can also Link and Combine this program with other software covered by + * the terms of any of the Free Software licenses or any of the Open Source + * Initiative approved licenses and Convey the resulting work. Corresponding + * source of such a combination shall include the source code for all other + * software used. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See COPYING file for full licensing terms. + * See https://www.nexedi.com/licensing for rationale and options. + */ +/*jslint nomen: true */ +/*global indexedDB, RSVP, IDBOpenDBRequest, DOMError, Event, Set, DOMException, + window*/ + +(function (indexedDB, RSVP, IDBOpenDBRequest, DOMError, DOMException) { + "use strict"; + + function waitForIDBRequest(request) { + return new RSVP.Promise(function (resolve, reject) { + request.onerror = reject; + request.onsuccess = resolve; + }); + } + + function waitForAllSynchronousCursor(request, callback) { + var force_cancellation = false; + + function canceller() { + force_cancellation = true; + } + + function resolver(resolve, reject) { + request.onerror = reject; + request.onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor && !force_cancellation) { + try { + callback(cursor); + } catch (error) { + reject(error); + } + // continue to next iteration + cursor["continue"](); + } else { + resolve(); + } + }; + } + return new RSVP.Promise(resolver, canceller); + } + + function waitForOpenIndexedDB(db_name, version, upgrade_handler, callback) { + var request; + + function canceller() { + if ((request !== undefined) && (request.result !== undefined)) { + request.result.close(); + } + } + + function resolver(resolve, reject) { + // Open DB // + request = indexedDB.open(db_name, version); + request.onerror = function (error) { + canceller(); + if ((error !== undefined) && + (error.target instanceof IDBOpenDBRequest) && + ((error.target.error instanceof DOMError) || + (error.target.error instanceof DOMException))) { + reject("Connection to: " + db_name + " failed: " + + error.target.error.message); + } else { + reject(error); + } + }; + + request.onabort = function () { + canceller(); + reject("Aborting connection to: " + db_name); + }; + + request.ontimeout = function () { + reject("Connection to: " + db_name + " timeout"); + }; + + request.onblocked = function () { + canceller(); + reject("Connection to: " + db_name + " was blocked"); + }; + + // Create DB if necessary // + request.onupgradeneeded = upgrade_handler; + + request.onversionchange = function () { + canceller(); + reject(db_name + " was upgraded"); + }; + + request.onsuccess = function () { + var result; + try { + result = callback(request.result); + } catch (error) { + reject(error); + } + return new RSVP.Queue() + .push(function () { + return result; + }) + .push(function (final_result) { + canceller(); + resolve(final_result); + }, function (error) { + canceller(); + reject(error); + }); + }; + } + + return new RSVP.Promise(resolver, canceller); + } + + function waitForTransaction(db, stores, flag, callback) { + var tx = db.transaction(stores, flag); + function canceller() { + try { + tx.abort(); + } catch (unused) { + // Transaction already finished + return; + } + } + function resolver(resolve, reject) { + var result; + try { + result = callback(tx); + } catch (error) { + reject(error); + } + tx.oncomplete = function () { + return new RSVP.Queue() + .push(function () { + return result; + }) + .push(resolve, function (error) { + canceller(); + reject(error); + }); + }; + tx.onerror = function (error) { + canceller(); + reject(error); + }; + tx.onabort = function (evt) { + reject(evt.target); + }; + return tx; + } + return new RSVP.Promise(resolver, canceller); + } + + window.waitForTransaction = waitForTransaction; + window.waitForOpenIndexedDB = waitForOpenIndexedDB; + window.waitForIDBRequest = waitForIDBRequest; + window.waitForAllSynchronousCursor = waitForAllSynchronousCursor; + +}(indexedDB, RSVP, IDBOpenDBRequest, DOMError, DOMException)); \ No newline at end of file diff --git a/src/jio.storage/indexeddbstorage.js b/src/jio.storage/indexeddbstorage.js index c7b89bdf50d0733e3d035fa532adea6e96aacf13..aae4221b7c6a75e1f70f8270c959bba6c962fc9f 100644 --- a/src/jio.storage/indexeddbstorage.js +++ b/src/jio.storage/indexeddbstorage.js @@ -42,11 +42,11 @@ */ /*jslint nomen: true */ -/*global indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest, - DOMError, Event*/ +/*global jIO, RSVP, Blob, Math, IDBKeyRange, Event, waitForOpenIndexedDB, + waitForIDBRequest, waitForAllSynchronousCursor, waitForTransaction*/ -(function (indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest, - DOMError) { +(function (jIO, RSVP, Blob, Math, IDBKeyRange, waitForOpenIndexedDB, + waitForIDBRequest, waitForAllSynchronousCursor, waitForTransaction) { "use strict"; // Read only as changing it can lead to data corruption @@ -96,143 +96,6 @@ store.createIndex("_id", "_id", {unique: false}); } - function waitForOpenIndexedDB(db_name, callback) { - var request; - - function canceller() { - if ((request !== undefined) && (request.result !== undefined)) { - request.result.close(); - } - } - - function resolver(resolve, reject) { - // Open DB // - request = indexedDB.open(db_name); - request.onerror = function (error) { - canceller(); - if ((error !== undefined) && - (error.target instanceof IDBOpenDBRequest) && - (error.target.error instanceof DOMError)) { - reject("Connection to: " + db_name + " failed: " + - error.target.error.message); - } else { - reject(error); - } - }; - - request.onabort = function () { - canceller(); - reject("Aborting connection to: " + db_name); - }; - - request.ontimeout = function () { - reject("Connection to: " + db_name + " timeout"); - }; - - request.onblocked = function () { - canceller(); - reject("Connection to: " + db_name + " was blocked"); - }; - - // Create DB if necessary // - request.onupgradeneeded = handleUpgradeNeeded; - - request.onversionchange = function () { - canceller(); - reject(db_name + " was upgraded"); - }; - - request.onsuccess = function () { - var result; - try { - result = callback(request.result); - } catch (error) { - reject(error); - } - return new RSVP.Queue() - .push(function () { - return result; - }) - .push(function (final_result) { - canceller(); - resolve(final_result); - }, function (error) { - canceller(); - reject(error); - }); - }; - } - - return new RSVP.Promise(resolver, canceller); - } - - function waitForTransaction(db, stores, flag, callback) { - var tx = db.transaction(stores, flag); - function canceller() { - try { - tx.abort(); - } catch (unused) { - // Transaction already finished - return; - } - } - function resolver(resolve, reject) { - var result; - try { - result = callback(tx); - } catch (error) { - reject(error); - } - tx.oncomplete = function () { - return new RSVP.Queue() - .push(function () { - return result; - }) - .push(resolve, function (error) { - canceller(); - reject(error); - }); - }; - tx.onerror = reject; - tx.onabort = reject; - } - return new RSVP.Promise(resolver, canceller); - } - - function waitForIDBRequest(request) { - return new RSVP.Promise(function (resolve, reject) { - request.onerror = reject; - request.onsuccess = resolve; - }); - } - - function waitForAllSynchronousCursor(request, callback) { - var force_cancellation = false; - - function canceller() { - force_cancellation = true; - } - - function resolver(resolve, reject) { - request.onerror = reject; - request.onsuccess = function (evt) { - var cursor = evt.target.result; - if (cursor && !force_cancellation) { - try { - callback(cursor); - } catch (error) { - reject(error); - } - // continue to next iteration - cursor["continue"](); - } else { - resolve(); - } - }; - } - return new RSVP.Promise(resolver, canceller); - } - IndexedDBStorage.prototype.buildQuery = function (options) { var result_list = [], context = this; @@ -254,21 +117,22 @@ return new RSVP.Queue() .push(function () { - return waitForOpenIndexedDB(context._database_name, function (db) { - return waitForTransaction(db, ["metadata"], "readonly", + return waitForOpenIndexedDB(context._database_name, undefined, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["metadata"], "readonly", function (tx) { - if (options.include_docs === true) { + if (options.include_docs === true) { + return waitForAllSynchronousCursor( + tx.objectStore("metadata").index("_id").openCursor(), + pushIncludedMetadata + ); + } return waitForAllSynchronousCursor( - tx.objectStore("metadata").index("_id").openCursor(), - pushIncludedMetadata + tx.objectStore("metadata").index("_id").openKeyCursor(), + pushMetadata ); - } - return waitForAllSynchronousCursor( - tx.objectStore("metadata").index("_id").openKeyCursor(), - pushMetadata - ); - }); - }); + }); + }); }) .push(function () { return result_list; @@ -279,12 +143,13 @@ var context = this; return new RSVP.Queue() .push(function () { - return waitForOpenIndexedDB(context._database_name, function (db) { - return waitForTransaction(db, ["metadata"], "readonly", - function (tx) { - return waitForIDBRequest(tx.objectStore("metadata").get(id)); - }); - }); + return waitForOpenIndexedDB(context._database_name, undefined, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["metadata"], "readonly", + function (tx) { + return waitForIDBRequest(tx.objectStore("metadata").get(id)); + }); + }); }) .push(function (evt) { if (evt.target.result) { @@ -307,19 +172,20 @@ return new RSVP.Queue() .push(function () { - return waitForOpenIndexedDB(context._database_name, function (db) { - return waitForTransaction(db, ["metadata", "attachment"], "readonly", - function (tx) { - return RSVP.all([ - waitForIDBRequest(tx.objectStore("metadata").get(id)), - waitForAllSynchronousCursor( - tx.objectStore("attachment").index("_id") - .openKeyCursor(IDBKeyRange.only(id)), - addEntry - ) - ]); - }); - }); + return waitForOpenIndexedDB(context._database_name, undefined, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["metadata", "attachment"], + "readonly", function (tx) { + return RSVP.all([ + waitForIDBRequest(tx.objectStore("metadata").get(id)), + waitForAllSynchronousCursor( + tx.objectStore("attachment").index("_id") + .openKeyCursor(IDBKeyRange.only(id)), + addEntry + ) + ]); + }); + }); }) .push(function (result_list) { var evt = result_list[0]; @@ -336,56 +202,58 @@ }; IndexedDBStorage.prototype.put = function (id, metadata) { - return waitForOpenIndexedDB(this._database_name, function (db) { - return waitForTransaction(db, ["metadata"], "readwrite", - function (tx) { - return waitForIDBRequest(tx.objectStore("metadata").put({ - "_id": id, - "doc": metadata - })); - }); - }); + return waitForOpenIndexedDB(this._database_name, undefined, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["metadata"], "readwrite", + function (tx) { + return waitForIDBRequest(tx.objectStore("metadata").put({ + "_id": id, + "doc": metadata + })); + }); + }); }; IndexedDBStorage.prototype.remove = function (id) { - return waitForOpenIndexedDB(this._database_name, function (db) { - return waitForTransaction(db, ["metadata", "attachment", "blob"], - "readwrite", function (tx) { - - var promise_list = [], - metadata_store = tx.objectStore("metadata"), - attachment_store = tx.objectStore("attachment"), - blob_store = tx.objectStore("blob"); - - function deleteAttachment(cursor) { - promise_list.push( - waitForIDBRequest(attachment_store.delete(cursor.primaryKey)) - ); - } - function deleteBlob(cursor) { - promise_list.push( - waitForIDBRequest(blob_store.delete(cursor.primaryKey)) - ); - } + return waitForOpenIndexedDB(this._database_name, undefined, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["metadata", "attachment", "blob"], + "readwrite", function (tx) { + + var promise_list = [], + metadata_store = tx.objectStore("metadata"), + attachment_store = tx.objectStore("attachment"), + blob_store = tx.objectStore("blob"); - return RSVP.all([ - waitForIDBRequest(metadata_store.delete(id)), - waitForAllSynchronousCursor( - attachment_store.index("_id") - .openKeyCursor(IDBKeyRange.only(id)), - deleteAttachment - ), - waitForAllSynchronousCursor( - blob_store.index("_id") - .openKeyCursor(IDBKeyRange.only(id)), - deleteBlob - ), - ]) - .then(function () { - return RSVP.all(promise_list); - }); - }); - }); + function deleteAttachment(cursor) { + promise_list.push( + waitForIDBRequest(attachment_store.delete(cursor.primaryKey)) + ); + } + function deleteBlob(cursor) { + promise_list.push( + waitForIDBRequest(blob_store.delete(cursor.primaryKey)) + ); + } + + return RSVP.all([ + waitForIDBRequest(metadata_store.delete(id)), + waitForAllSynchronousCursor( + attachment_store.index("_id") + .openKeyCursor(IDBKeyRange.only(id)), + deleteAttachment + ), + waitForAllSynchronousCursor( + blob_store.index("_id") + .openKeyCursor(IDBKeyRange.only(id)), + deleteBlob + ), + ]) + .then(function () { + return RSVP.all(promise_list); + }); + }); + }); }; IndexedDBStorage.prototype.getAttachment = function (id, name, options) { @@ -416,65 +284,66 @@ return new RSVP.Queue() .push(function () { - return waitForOpenIndexedDB(db_name, function (db) { - return waitForTransaction(db, ["blob"], "readonly", - function (tx) { - var key_path = buildKeyPath([id, name]), - blob_store = tx.objectStore("blob"), - start_index, - end_index, - promise_list = []; - - - start_index = Math.floor(start / UNITE); - if (end !== undefined) { - end_index = Math.floor(end / UNITE); - if (end % UNITE === 0) { - end_index -= 1; - } - } - - function getBlobKey(cursor) { - var index = parseInt( - cursor.primaryKey.slice(key_path.length + 1), - 10 - ), - i; - - if ((start !== 0) && (index < start_index)) { - // No need to fetch blobs at the start - return; - } - if ((end !== undefined) && (index > end_index)) { - // No need to fetch blobs at the end - return; + return waitForOpenIndexedDB(db_name, undefined, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["blob"], "readonly", + function (tx) { + var key_path = buildKeyPath([id, name]), + blob_store = tx.objectStore("blob"), + start_index, + end_index, + promise_list = []; + + + start_index = Math.floor(start / UNITE); + if (end !== undefined) { + end_index = Math.floor(end / UNITE); + if (end % UNITE === 0) { + end_index -= 1; + } } - i = index - start_index; - // Extend array size - while (i > promise_list.length) { - promise_list.push(null); - i -= 1; + function getBlobKey(cursor) { + var index = parseInt( + cursor.primaryKey.slice(key_path.length + 1), + 10 + ), + i; + + if ((start !== 0) && (index < start_index)) { + // No need to fetch blobs at the start + return; + } + if ((end !== undefined) && (index > end_index)) { + // No need to fetch blobs at the end + return; + } + + i = index - start_index; + // Extend array size + while (i > promise_list.length) { + promise_list.push(null); + i -= 1; + } + // Sort the blob by their index + promise_list.splice( + index - start_index, + 0, + waitForIDBRequest(blob_store.get(cursor.primaryKey)) + ); } - // Sort the blob by their index - promise_list.splice( - index - start_index, - 0, - waitForIDBRequest(blob_store.get(cursor.primaryKey)) - ); - } - // Get all blob keys to check if they must be fetched - return waitForAllSynchronousCursor( - blob_store.index("_id_attachment") - .openKeyCursor(IDBKeyRange.only([id, name])), - getBlobKey - ) - .then(function () { - return RSVP.all(promise_list); - }); - }); - }); + // Get all blob keys to check if they must be fetched + return waitForAllSynchronousCursor( + blob_store.index("_id_attachment") + .openKeyCursor(IDBKeyRange.only([id, name])), + getBlobKey + ) + .then(function () { + return RSVP.all(promise_list); + }); + }); + }); }) .push(function (result_list) { // No need to keep the IDB open @@ -501,46 +370,47 @@ // Request the full blob return new RSVP.Queue() .push(function () { - return waitForOpenIndexedDB(db_name, function (db) { - return waitForTransaction(db, ["attachment", "blob"], "readonly", - function (tx) { - var key_path = buildKeyPath([id, name]), - attachment_store = tx.objectStore("attachment"), - blob_store = tx.objectStore("blob"); + return waitForOpenIndexedDB(db_name, undefined, handleUpgradeNeeded, + function (db) { + return waitForTransaction(db, ["attachment", "blob"], "readonly", + function (tx) { + var key_path = buildKeyPath([id, name]), + attachment_store = tx.objectStore("attachment"), + blob_store = tx.objectStore("blob"); - function getBlob(cursor) { - var index = parseInt( - cursor.primaryKey.slice(key_path.length + 1), - 10 - ), - i = index; - // Extend array size - while (i > array_buffer_list.length) { - array_buffer_list.push(null); - i -= 1; + function getBlob(cursor) { + var index = parseInt( + cursor.primaryKey.slice(key_path.length + 1), + 10 + ), + i = index; + // Extend array size + while (i > array_buffer_list.length) { + array_buffer_list.push(null); + i -= 1; + } + // Sort the blob by their index + array_buffer_list.splice( + index, + 0, + cursor.value.blob + ); } - // Sort the blob by their index - array_buffer_list.splice( - index, - 0, - cursor.value.blob - ); - } - - return RSVP.all([ - // Get the attachment info (mime type) - waitForIDBRequest(attachment_store.get( - key_path - )), - // Get all needed blobs - waitForAllSynchronousCursor( - blob_store.index("_id_attachment") - .openCursor(IDBKeyRange.only([id, name])), - getBlob - ) - ]); - }); - }); + + return RSVP.all([ + // Get the attachment info (mime type) + waitForIDBRequest(attachment_store.get( + key_path + )), + // Get all needed blobs + waitForAllSynchronousCursor( + blob_store.index("_id_attachment") + .openCursor(IDBKeyRange.only([id, name])), + getBlob + ) + ]); + }); + }); }) .push(function (result_list) { @@ -591,103 +461,106 @@ handled_size += UNITE; } - return waitForOpenIndexedDB(db_name, function (db) { - return waitForTransaction(db, ["attachment", "blob"], "readwrite", - function (tx) { - var blob_store, - promise_list, - delete_promise_list = [], - key_path = buildKeyPath([id, name]), - i; - // First write the attachment info on top of previous - promise_list = [ - waitForIDBRequest(tx.objectStore("attachment").put({ - "_key_path": key_path, - "_id": id, - "_attachment": name, - "info": { - "content_type": blob.type, - "length": blob.size - } - })) - ]; - // Then, write all blob parts on top of previous - blob_store = tx.objectStore("blob"); - for (i = 0; i < blob_part.length; i += 1) { - promise_list.push( - waitForIDBRequest(blob_store.put({ - "_key_path": buildKeyPath([id, name, i]), - "_id" : id, - "_attachment" : name, - "_part" : i, - "blob": blob_part[i] + return waitForOpenIndexedDB(db_name, undefined, handleUpgradeNeeded, + function (db) { + return waitForTransaction(db, ["attachment", "blob"], "readwrite", + function (tx) { + var blob_store, + promise_list, + delete_promise_list = [], + key_path = buildKeyPath([id, name]), + i; + // First write the attachment info on top of previous + promise_list = [ + waitForIDBRequest(tx.objectStore("attachment").put({ + "_key_path": key_path, + "_id": id, + "_attachment": name, + "info": { + "content_type": blob.type, + "length": blob.size + } })) - ); - } + ]; + // Then, write all blob parts on top of previous + blob_store = tx.objectStore("blob"); + for (i = 0; i < blob_part.length; i += 1) { + promise_list.push( + waitForIDBRequest(blob_store.put({ + "_key_path": buildKeyPath([id, name, i]), + "_id" : id, + "_attachment" : name, + "_part" : i, + "blob": blob_part[i] + })) + ); + } - function deleteEntry(cursor) { - var index = parseInt( - cursor.primaryKey.slice(key_path.length + 1), - 10 - ); - if (index >= blob_part.length) { - delete_promise_list.push( - waitForIDBRequest(blob_store.delete(cursor.primaryKey)) + function deleteEntry(cursor) { + var index = parseInt( + cursor.primaryKey.slice(key_path.length + 1), + 10 ); + if (index >= blob_part.length) { + delete_promise_list.push( + waitForIDBRequest(blob_store.delete(cursor.primaryKey)) + ); + } } - } - // Finally, remove all remaining blobs - promise_list.push( - waitForAllSynchronousCursor( - blob_store.index("_id_attachment") - .openKeyCursor(IDBKeyRange.only([id, name])), - deleteEntry - ) - ); + // Finally, remove all remaining blobs + promise_list.push( + waitForAllSynchronousCursor( + blob_store.index("_id_attachment") + .openKeyCursor(IDBKeyRange.only([id, name])), + deleteEntry + ) + ); - return RSVP.all(promise_list) - .then(function () { - if (delete_promise_list.length) { - return RSVP.all(delete_promise_list); - } - }); - }); - }); + return RSVP.all(promise_list) + .then(function () { + if (delete_promise_list.length) { + return RSVP.all(delete_promise_list); + } + }); + }); + }); }); }; IndexedDBStorage.prototype.removeAttachment = function (id, name) { - return waitForOpenIndexedDB(this._database_name, function (db) { - return waitForTransaction(db, ["attachment", "blob"], "readwrite", - function (tx) { - var promise_list = [], - attachment_store = tx.objectStore("attachment"), - blob_store = tx.objectStore("blob"); - - function deleteEntry(cursor) { - promise_list.push( - waitForIDBRequest(blob_store.delete(cursor.primaryKey)) - ); - } + return waitForOpenIndexedDB(this._database_name, undefined, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["attachment", "blob"], "readwrite", + function (tx) { + var promise_list = [], + attachment_store = tx.objectStore("attachment"), + blob_store = tx.objectStore("blob"); - return RSVP.all([ - waitForIDBRequest( - attachment_store.delete(buildKeyPath([id, name])) - ), - waitForAllSynchronousCursor( - blob_store.index("_id_attachment") - .openKeyCursor(IDBKeyRange.only([id, name])), - deleteEntry - ) - ]) - .then(function () { - return RSVP.all(promise_list); - }); + function deleteEntry(cursor) { + promise_list.push( + waitForIDBRequest(blob_store.delete(cursor.primaryKey)) + ); + } + + return RSVP.all([ + waitForIDBRequest( + attachment_store.delete(buildKeyPath([id, name])) + ), + waitForAllSynchronousCursor( + blob_store.index("_id_attachment") + .openKeyCursor(IDBKeyRange.only([id, name])), + deleteEntry + ) + ]) + .then(function () { + return RSVP.all(promise_list); + }); - }); - }); + }); + }); }; jIO.addStorage("indexeddb", IndexedDBStorage); -}(indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest, DOMError)); +}(jIO, RSVP, Blob, Math, IDBKeyRange, waitForOpenIndexedDB, waitForIDBRequest, + waitForAllSynchronousCursor, waitForTransaction)); diff --git a/src/jio.storage/indexstorage2.js b/src/jio.storage/indexstorage2.js new file mode 100644 index 0000000000000000000000000000000000000000..aed84b93037c9b12fdf56d3f95bbdcf37401821d --- /dev/null +++ b/src/jio.storage/indexstorage2.js @@ -0,0 +1,441 @@ +/* + * Copyright 2019, Nexedi SA + * + * This program is free software: you can Use, Study, Modify and Redistribute + * it under the terms of the GNU General Public License version 3, or (at your + * option) any later version, as published by the Free Software Foundation. + * + * You can also Link and Combine this program with other software covered by + * the terms of any of the Free Software licenses or any of the Open Source + * Initiative approved licenses and Convey the resulting work. Corresponding + * source of such a combination shall include the source code for all other + * software used. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See COPYING file for full licensing terms. + * See https://www.nexedi.com/licensing for rationale and options. + */ +/*jslint nomen: true */ +/*global indexedDB, jIO, RSVP, Event, parseStringToObject, Set, + waitForTransaction, waitForAllSynchronousCursor, waitForIDBRequest, + waitForOpenIndexedDB*/ + +(function (indexedDB, jIO, RSVP, parseStringToObject, waitForTransaction, + waitForAllSynchronousCursor, waitForIDBRequest, waitForOpenIndexedDB) { + "use strict"; + + function IndexStorage2(description) { + if (typeof description.database !== "string" || + description.database === "") { + throw new TypeError("IndexStorage2 'database' description property " + + "must be a non-empty string"); + } + if (description.index_keys && !(description.index_keys instanceof Array)) { + throw new TypeError("IndexStorage2 'index_keys' description property " + + "must be an Array"); + } + if (description.version && (typeof description.version !== "number")) { + throw new TypeError("IndexStorage2 'version' description property " + + "must be a number"); + } + this._sub_storage_description = description.sub_storage; + this._sub_storage = jIO.createJIO(description.sub_storage); + this._database_name = "jio:" + description.database; + this._index_keys = description.index_keys || []; + this._version = description.version; + this._signature_storage_name = description.database + "_signatures"; + } + + IndexStorage2.prototype.hasCapacity = function (name) { + return (name === "query") || (name === "limit") || (name === "list") || + this._sub_storage.hasCapacity(name); + }; + + function isSubset(set1, set2) { + var i, values; + values = Array.from(set2); + for (i = 0; i < values.length; i += 1) { + if (!set1.has(values[i])) { + return false; + } + } + return true; + } + + function filterDocValues(doc, keys) { + var filtered_doc = {}, i; + if (keys) { + for (i = 0; i < keys.length; i += 1) { + filtered_doc[keys[i]] = doc[keys[i]]; + } + return filtered_doc; + } + return doc; + } + + function getCursorResult(cursor, limit) { + var result = [], count = 0; + function pushLimitedMetadata(cursor) { + if (count >= limit[0] && count < limit[1]) { + result.push({id: cursor.primaryKey, value: {}}); + } + count += 1; + } + return waitForAllSynchronousCursor(cursor, pushLimitedMetadata) + .then(function () { + return result; + }); + } + + function waitForRepairableOpenIndexedDB(db_name, version, index_keys, + sub_storage_description, signature_storage_name, upgrade_handler, + callback) { + var handleUpgradeNeeded = function (evt) { + return upgrade_handler(evt, index_keys, sub_storage_description, + signature_storage_name); + }; + return waitForOpenIndexedDB(db_name, version, handleUpgradeNeeded, + callback); + } + + function VirtualIDB(description) { + this._operations = description.operations; + } + + function virtualOperation(type, context, function_arguments) { + var cancel_callback, operation = {}, promise; + function resolver(resolve, reject) { + cancel_callback = reject; + operation.type = type; + operation.arguments = function_arguments; + operation.onsuccess = resolve; + operation.onerror = reject; + } + function canceller() { + cancel_callback(); + } + promise = new RSVP.Promise(resolver, canceller); + operation.promise = promise; + context._operations.push(operation); + return promise; + } + + VirtualIDB.prototype.hasCapacity = function (name) { + return (name === "list"); + }; + + VirtualIDB.prototype.put = function () { + return virtualOperation("put", this, arguments); + }; + + VirtualIDB.prototype.remove = function () { + return virtualOperation("remove", this, arguments); + }; + + VirtualIDB.prototype.get = function () { + return virtualOperation("get", this, arguments); + }; + + VirtualIDB.prototype.buildQuery = function () { + return virtualOperation("buildQuery", this, arguments); + }; + + VirtualIDB.prototype.allAttachments = function () { + return {}; + }; + + jIO.addStorage("virtualidb", VirtualIDB); + + function getRepairStorage(operations, sub_storage_description, + signature_storage_name) { + return jIO.createJIO({ + type: "replicate", + local_sub_storage: sub_storage_description, + remote_sub_storage: { + type: "virtualidb", + operations: operations + }, + signature_sub_storage: { + type: "query", + sub_storage: { + type: "indexeddb", + database: signature_storage_name + } + }, + check_remote_modification: false, + check_remote_creation: false, + check_remote_deletion: false, + conflict_handling: 1, + parallel_operation_amount: 16 + }); + } + + function handleVirtualGetSuccess(id, onsuccess, onerror) { + return function (result) { + if (result.target.result === undefined) { + return onerror(new jIO.util.jIOError("Cannot find document: " + + id, 404)); + } + return onsuccess(result.target.result.doc); + }; + } + + function processVirtualOperation(operation, store, index_keys, disable_get) { + var request, get_success_handler; + if (operation.promise.isRejected) { + return; + } + if (operation.type === "put") { + request = store.put({ + id: operation.arguments[0], + doc: filterDocValues(operation.arguments[1], index_keys), + }); + request.onerror = operation.onerror; + return {request: request, onsuccess: operation.onsuccess}; + } + if (operation.type === "get") { + // if storage was cleared, get can return without checking the database + if (disable_get) { + operation.onerror(new jIO.util.jIOError("Cannot find document: " + + operation.arguments[0], 404)); + } else { + get_success_handler = handleVirtualGetSuccess(operation.arguments[0], + operation.onsuccess, operation.onerror); + request = store.get(operation.arguments[0]); + request.onerror = operation.onerror; + return {request: request, onsuccess: get_success_handler}; + } + } + if (operation.type === "buildQuery") { + request = store.getAllKeys(); + request.oerror = operation.onerror; + return {request: request, onsuccess: operation.onsuccess}; + } + if (operation.type === "remove") { + request = store.delete(operation.arguments[0]); + request.onerror = operation.onerror; + return {request: request, onsuccess: operation.onsuccess}; + } + } + + function repairInTransaction(sub_storage_description, transaction, + index_keys, signature_storage_name, clear_storage) { + var repair_promise, repeatUntilPromiseFulfilled, store, + operations = []; + if (clear_storage) { + indexedDB.deleteDatabase("jio:" + signature_storage_name); + } + store = transaction.objectStore("index-store"); + repair_promise = getRepairStorage(operations, + sub_storage_description, signature_storage_name).repair(); + repeatUntilPromiseFulfilled = function repeatUntilPromiseFulfilled( + continuation_request, + continuation_resolve + ) { + var operation_result, next_continuation_request, + next_continuation_resolve; + continuation_request.onsuccess = function () { + if (continuation_resolve) { + continuation_resolve.apply(null, arguments); + } + while (operations.length !== 0) { + operation_result = processVirtualOperation(operations.shift(), store, + index_keys, clear_storage); + // use the current request to continue the repeat loop if possible + if (next_continuation_request && operation_result) { + operation_result.request.onsuccess = operation_result.onsuccess; + } else if (operation_result) { + next_continuation_request = operation_result.request; + next_continuation_resolve = operation_result.onsuccess; + } + } + if (repair_promise.isRejected) { + transaction.abort(); + return; + } + if (repair_promise.isFulfilled) { + return; + } + return repeatUntilPromiseFulfilled(next_continuation_request || + store.get("inexistent"), next_continuation_resolve); + }; + }; + return repeatUntilPromiseFulfilled(store.get("inexistent")); + } + + function handleUpgradeNeeded(evt, index_keys, sub_storage_description, + signature_storage_name) { + var db = evt.target.result, store, i, current_indices, required_indices; + required_indices = new Set(index_keys.map(function (name) { + return "Index-" + name; + })); + if (db.objectStoreNames[0] === "index-store") { + store = evt.target.transaction.objectStore("index-store"); + } + + current_indices = new Set(store ? store.indexNames : []); + if (isSubset(current_indices, required_indices)) { + if (store) { + for (i = 0; i < store.indexNames.length; i += 1) { + if (!required_indices.has(store.indexNames[i])) { + store.deleteIndex(store.indexNames[i]); + } + } + } + } else { + if (store) { + db.deleteObjectStore("index-store"); + current_indices.clear(); + } + store = db.createObjectStore("index-store", { + keyPath: "id", + autoIncrement: false + }); + for (i = 0; i < index_keys.length; i += 1) { + store.createIndex("Index-" + index_keys[i], + "doc." + index_keys[i], { unique: false }); + } + return repairInTransaction(sub_storage_description, + evt.target.transaction, index_keys, signature_storage_name, true); + } + } + + IndexStorage2.prototype._runQuery = function (key, value, limit) { + var context = this; + + return RSVP.Queue() + .push(function () { + return waitForRepairableOpenIndexedDB(context._database_name, + context._version, context._index_keys, + context._sub_storage_description, context._signature_storage_name, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["index-store"], "readonly", + function (tx) { + if (limit) { + return getCursorResult(tx.objectStore("index-store") + .index("Index-" + key).openCursor(value), limit); + } + return waitForIDBRequest(tx.objectStore("index-store") + .index("Index-" + key).getAllKeys(value)); + }); + }); + }) + .push(function (result) { + if (limit) { + return result; + } + return result.target.result.map(function (item) { + return {id: item, value: {}}; + }); + }); + }; + + IndexStorage2.prototype.buildQuery = function (options) { + var context = this, query; + if (options.query && !options.include_docs && !options.sort_on && + !options.select_list) { + query = parseStringToObject(options.query); + if (query.type === "simple") { + if (context._index_keys.indexOf(query.key) !== -1) { + return context._runQuery(query.key, query.value, options.limit) + .then(function (result) { + return result; + }); + } + } + } + return context._sub_storage.allDocs(options) + .push(function (result) { + return result.data.rows; + }); + }; + + IndexStorage2.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, arguments); + }; + + IndexStorage2.prototype._put = function (id, value) { + var context = this; + if (context._index_keys.length === 0) { + return; + } + return waitForRepairableOpenIndexedDB(context._database_name, + context._version, context._index_keys, context._sub_storage_description, + context._signature_storage_name, handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["index-store"], "readwrite", + function (tx) { + return waitForIDBRequest(tx.objectStore("index-store").put({ + "id": id, + "doc": filterDocValues(value, context._index_keys) + })); + }); + }); + }; + + IndexStorage2.prototype.put = function (id, value) { + var context = this; + return context._sub_storage.put(id, value) + .push(function () { + return context._put(id, value); + }); + }; + + IndexStorage2.prototype.post = function (value) { + var context = this; + return context._sub_storage.post(value) + .push(function (id) { + return context._put(id, value) + .then(function () { + return id; + }); + }); + }; + + IndexStorage2.prototype.remove = function (id) { + var context = this; + return context._sub_storage.remove(id) + .push(function () { + return waitForRepairableOpenIndexedDB(context._database_name, + context._version, context._index_keys, + context._sub_storage_description, context._signature_storage_name, + handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["index-store"], "readwrite", + function (tx) { + return waitForIDBRequest(tx.objectStore("index-store") + .delete(id)); + }); + }); + }); + }; + + IndexStorage2.prototype.repair = function () { + var context = this; + return waitForRepairableOpenIndexedDB(context._database_name, + context._version, context._index_keys, context._sub_storage_description, + context._signature_storage_name, handleUpgradeNeeded, function (db) { + return waitForTransaction(db, ["index-store"], "readwrite", + function (tx) { + return repairInTransaction(context._sub_storage_description, tx, + context._index_keys, context._signature_storage_name); + }); + }); + }; + + IndexStorage2.prototype.getAttachment = function () { + return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); + }; + + IndexStorage2.prototype.putAttachment = function () { + return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + }; + + IndexStorage2.prototype.removeAttachment = function () { + return this._sub_storage.removeAttachment.apply(this._sub_storage, + arguments); + }; + + jIO.addStorage("index2", IndexStorage2); +}(indexedDB, jIO, RSVP, parseStringToObject, waitForTransaction, + waitForAllSynchronousCursor, waitForIDBRequest, waitForOpenIndexedDB)); \ No newline at end of file diff --git a/src/jio.storage/liststorage.js b/src/jio.storage/liststorage.js new file mode 100644 index 0000000000000000000000000000000000000000..dd5311a8762fa2fd9c6f3c3e6bbd29cb453ca613 --- /dev/null +++ b/src/jio.storage/liststorage.js @@ -0,0 +1,91 @@ +/* + * Copyright 2019, Nexedi SA + * + * This program is free software: you can Use, Study, Modify and Redistribute + * it under the terms of the GNU General Public License version 3, or (at your + * option) any later version, as published by the Free Software Foundation. + * + * You can also Link and Combine this program with other software covered by + * the terms of any of the Free Software licenses or any of the Open Source + * Initiative approved licenses and Convey the resulting work. Corresponding + * source of such a combination shall include the source code for all other + * software used. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See COPYING file for full licensing terms. + * See https://www.nexedi.com/licensing for rationale and options. + */ +/*jslint nomen: true*/ +/*global Set*/ +(function (jIO) { + "use strict"; + + function ListStorage(spec) { + this._sub_storage = jIO.createJIO(spec.sub_storage); + this._signature_storage = jIO.createJIO(spec.signature_storage); + } + + ListStorage.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, arguments); + }; + ListStorage.prototype.allAttachments = function () { + return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); + }; + ListStorage.prototype.post = function (value) { + var gadget = this; + return gadget._sub_storage.post(value) + .push(function (id) { + return gadget._signature_storage.put(id, {}) + .push(function () { + return id; + }); + }); + }; + ListStorage.prototype.put = function (id, value) { + var gadget = this; + return gadget._sub_storage.put(id, value) + .push(function (result) { + return gadget._signature_storage.put(id, {}) + .push(function () { + return result; + }); + }); + }; + ListStorage.prototype.remove = function (id) { + var gadget = this; + return gadget._sub_storage.remove(id) + .push(function (result) { + return gadget._signature_storage.remove(id) + .push(function () { + return result; + }); + }); + }; + ListStorage.prototype.getAttachment = function () { + return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); + }; + ListStorage.prototype.putAttachment = function () { + return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + }; + ListStorage.prototype.removeAttachment = function () { + return this._sub_storage.removeAttachment.apply(this._sub_storage, + arguments); + }; + ListStorage.prototype.repair = function () { + return this._sub_storage.repair.apply(this._sub_storage, arguments); + }; + ListStorage.prototype.hasCapacity = function (name) { + if (name === "list") { + return this._signature_storage.hasCapacity('list'); + } + return false; + }; + ListStorage.prototype.buildQuery = function () { + return this._signature_storage.buildQuery(); + }; + + jIO.addStorage('list', ListStorage); + +}(jIO)); \ No newline at end of file diff --git a/src/jio.storage/nocapacitystorage.js b/src/jio.storage/nocapacitystorage.js new file mode 100644 index 0000000000000000000000000000000000000000..f746b8838d90e1a42e9b8c303baa83fabe807626 --- /dev/null +++ b/src/jio.storage/nocapacitystorage.js @@ -0,0 +1,59 @@ +/* + * Copyright 2019, Nexedi SA + * + * This program is free software: you can Use, Study, Modify and Redistribute + * it under the terms of the GNU General Public License version 3, or (at your + * option) any later version, as published by the Free Software Foundation. + * + * You can also Link and Combine this program with other software covered by + * the terms of any of the Free Software licenses or any of the Open Source + * Initiative approved licenses and Convey the resulting work. Corresponding + * source of such a combination shall include the source code for all other + * software used. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See COPYING file for full licensing terms. + * See https://www.nexedi.com/licensing for rationale and options. + */ +/*jslint nomen: true*/ +(function (jIO) { + "use strict"; + + function NoCapacityStorage(spec) { + this._sub_storage = jIO.createJIO(spec.sub_storage); + } + + NoCapacityStorage.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, arguments); + }; + NoCapacityStorage.prototype.allAttachments = function () { + return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); + }; + NoCapacityStorage.prototype.post = function () { + return this._sub_storage.post.apply(this._sub_storage, arguments); + }; + NoCapacityStorage.prototype.put = function () { + return this._sub_storage.put.apply(this._sub_storage, arguments); + }; + NoCapacityStorage.prototype.remove = function () { + return this._sub_storage.remove.apply(this._sub_storage, arguments); + }; + NoCapacityStorage.prototype.getAttachment = function () { + return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); + }; + NoCapacityStorage.prototype.putAttachment = function () { + return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + }; + NoCapacityStorage.prototype.removeAttachment = function () { + return this._sub_storage.removeAttachment.apply(this._sub_storage, + arguments); + }; + NoCapacityStorage.prototype.repair = function () { + return this._sub_storage.repair.apply(this._sub_storage, arguments); + }; + + jIO.addStorage('nocapacity', NoCapacityStorage); + +}(jIO)); \ No newline at end of file diff --git a/test/jio.storage/indexstorage2.tests.js b/test/jio.storage/indexstorage2.tests.js new file mode 100644 index 0000000000000000000000000000000000000000..50391daffe31c9d27f64432e16377eed865cc953 --- /dev/null +++ b/test/jio.storage/indexstorage2.tests.js @@ -0,0 +1,1331 @@ +/* + * Copyright 2019, Nexedi SA + * + * This program is free software: you can Use, Study, Modify and Redistribute + * it under the terms of the GNU General Public License version 3, or (at your + * option) any later version, as published by the Free Software Foundation. + * + * You can also Link and Combine this program with other software covered by + * the terms of any of the Free Software licenses or any of the Open Source + * Initiative approved licenses and Convey the resulting work. Corresponding + * source of such a combination shall include the source code for all other + * software used. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See COPYING file for full licensing terms. + * See https://www.nexedi.com/licensing for rationale and options. + */ +/*jslint nomen: true */ +/*global indexedDB, Blob*/ +(function (jIO, QUnit, indexedDB, Blob) { + "use strict"; + var test = QUnit.test, + stop = QUnit.stop, + start = QUnit.start, + ok = QUnit.ok, + expect = QUnit.expect, + deepEqual = QUnit.deepEqual, + equal = QUnit.equal, + module = QUnit.module, + throws = QUnit.throws; + + function deleteIndexStorage2(storage) { + return new RSVP.Promise(function resolver(resolve, reject) { + var storage_deletion_request = indexedDB.deleteDatabase( + storage.__storage._database_name + ), signature_deletion_request = indexedDB.deleteDatabase( + "jio:" + storage.__storage._signature_storage_name + ); + storage_deletion_request.onerror = reject; + storage_deletion_request.onblocked = reject; + storage_deletion_request.onsuccess = resolve; + signature_deletion_request.onerror = reject; + signature_deletion_request.onblocked = reject; + signature_deletion_request.onsuccess = resolve; + }); + } + + function idCompare(value1, value2) { + if (value1.id > value2.id) { + return 1; + } + if (value1.id < value2.id) { + return -1; + } + return 0; + } + + ///////////////////////////////////////////////////////////////// + // Custom test substorage definition + ///////////////////////////////////////////////////////////////// + function DummyStorage3() { + return this; + } + jIO.addStorage("dummystorage3", DummyStorage3); + + ///////////////////////////////////////////////////////////////// + // indexStorage2.constructor + ///////////////////////////////////////////////////////////////// + module("indexStorage2.constructor", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("Constructor without index_keys", function () { + this.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + sub_storage: { + type: "dummystorage3" + } + }); + + equal(this.jio.__type, "index2"); + equal(this.jio.__storage._sub_storage.__type, "dummystorage3"); + equal(this.jio.__storage._database_name, "jio:index2_test"); + equal(this.jio.__storage._signature_storage_name, "index2_test_signatures"); + deepEqual(this.jio.__storage._index_keys, []); + }); + + test("Constructor incorrect description values", function () { + + throws( + function () { + this.jio = jIO.createJIO({ + type: "index2", + database: 44, + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + }, + function (error) { + ok(error instanceof TypeError); + equal(error.message, "IndexStorage2 'database' description property " + + "must be a non-empty string"); + return true; + } + ); + + throws( + function () { + this.jio = jIO.createJIO({ + type: "index2", + database: "", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + }, + function (error) { + ok(error instanceof TypeError); + equal(error.message, "IndexStorage2 'database' description property " + + "must be a non-empty string"); + return true; + } + ); + + throws( + function () { + this.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: "index_key", + sub_storage: { + type: "dummystorage3" + } + }); + }, + function (error) { + ok(error instanceof TypeError); + equal(error.message, "IndexStorage2 'index_keys' description property" + + " must be an Array"); + return true; + } + ); + throws( + function () { + this.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + version: "1", + sub_storage: { + type: "dummystorage3" + } + }); + }, + function (error) { + ok(error instanceof TypeError); + equal(error.message, "IndexStorage2 'version' description property" + + " must be a number"); + return true; + } + ); + }); + + test("Constructor with index_keys and version", function () { + this.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + version: 4, + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + + equal(this.jio.__type, "index2"); + equal(this.jio.__storage._sub_storage.__type, "dummystorage3"); + equal(this.jio.__storage._database_name, "jio:index2_test"); + equal(this.jio.__storage._version, 4); + equal(this.jio.__storage._signature_storage_name, "index2_test_signatures"); + deepEqual(this.jio.__storage._sub_storage_description, + {type: "dummystorage3"}); + deepEqual(this.jio.__storage._index_keys, ["a", "b"]); + }); + + ///////////////////////////////////////////////////////////////// + // indexStorage2.hasCapacity + ///////////////////////////////////////////////////////////////// + module("indexStorage2.hasCapacity", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("Test various capacities", function () { + this.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + sub_storage: { + type: "dummystorage3" + } + }); + + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "sort"); + }; + + throws( + function () { + this.jio.hasCapacity("non"); + }, + function (error) { + ok(error instanceof jIO.util.jIOError); + equal(error.status_code, 501); + equal(error.message, + "Capacity 'non' is not implemented on 'dummystorage3'"); + return true; + } + ); + + ok(this.jio.hasCapacity("query")); + ok(this.jio.hasCapacity("limit")); + ok(this.jio.hasCapacity("sort")); + }); + + ///////////////////////////////////////////////////////////////// + // indexStorage2.get + ///////////////////////////////////////////////////////////////// + module("indexStorage2.get", { + setup: function () { + this.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: [], + sub_storage: { + type: "dummystorage3" + } + }); + }, + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("Get calls substorage", function () { + var context = this; + stop(); + expect(2); + + DummyStorage3.prototype.get = function (id) { + equal(id, "32"); + return {"a": 3, "b": 2, "c": 8}; + }; + + context.jio.get("32") + .then(function (result) { + deepEqual(result, {"a": 3, "b": 2, "c": 8}); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // indexStorage2.buildQuery + ///////////////////////////////////////////////////////////////// + module("indexStorage2.buildQuery", { + setup: function () { + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "list") || (name === "include") || (name === "select"); + }; + DummyStorage3.prototype.buildQuery = function () { + return []; + }; + }, + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + + test("Sub-storage handles empty options", function () { + var context = this, call_count = 0; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(2); + + DummyStorage3.prototype.put = function (id) { + return id; + }; + + DummyStorage3.prototype.buildQuery = function (options) { + call_count += 1; + if (call_count === 1) { + return []; + } + if (options.include_docs !== true) { + return [ + {id: "2", value: {}}, + {id: "32", value: {}}, + {id: "16", value: {}}, + {id: "21", value: {}} + ]; + } + return []; + }; + + RSVP.all([ + context.jio.put("2", {"a": "close", "b": 45}), + context.jio.put("16", {"a": "value", "b": 5}), + context.jio.put("21", {"a": "advice", "b": 12}), + context.jio.put("32", {"a": "recieve", "b": 76}) + ]) + .then(function () { + return context.jio.allDocs(); + }) + .then(function (result) { + equal(result.data.total_rows, 4); + deepEqual(result.data.rows.sort(idCompare), [{id: "16", value: {}}, + {id: "2", value: {}}, {id: "21", value: {}}, {id: "32", value: {}}]); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + test("Sub storage capacities are used by default", function () { + var context = this, kemp = 0; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b", "c"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(3); + + DummyStorage3.prototype.put = function (id) { + return id; + }; + + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "list") || (name === "include") || (name === "select") + || (name === "sort") || (name === "limit"); + }; + + DummyStorage3.prototype.buildQuery = function (options) { + kemp += 1; + if (kemp === 1) { + return []; + } + if (options.include_docs === true) { + return []; + } + deepEqual(options, {include_docs: false, select_list: ["a", "c"], + limit: 3, sort_on: [["a", "descending"], ["b", "ascending"]]}); + return [ + {"id": "21", "value": {"a": "value", "c": "4"}}, + {"id": "16", "value": {"a": "value", "c": "54"}}, + {"id": "1", "value": {"a": "exhalt", "c": "28"}} + ]; + }; + + RSVP.all([ + context.jio.put("2", {"a": "close", "b": 45, "c": "78"}), + context.jio.put("16", {"a": "value", "b": 5, "c": "54"}), + context.jio.put("21", {"a": "value", "b": 12, "c": "4"}), + context.jio.put("7", {"a": "device", "b": 83, "c": "26"}), + context.jio.put("1", {"a": "exhalt", "b": 68, "c": "28"}) + ]) + .then(function () { + return context.jio.allDocs({select_list: ["a", "c"], limit: 3, + sort_on: [["a", "descending"], ["b", "ascending"]], + include_docs: false, }); + }) + .then(function (result) { + equal(result.data.total_rows, 3); + deepEqual(result.data.rows, [ + {"id": "21", "value": {"a": "value", "c": "4"}}, + {"id": "16", "value": {"a": "value", "c": "54"}}, + {"id": "1", "value": {"a": "exhalt", "c": "28"}} + ]); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + test("Simple query matching single object", function () { + var context = this; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(4); + + DummyStorage3.prototype.put = function (id, value) { + equal(id, "32"); + deepEqual(value, {a: "3", b: "2"}); + return id; + }; + + context.jio.put("32", {"a": "3", "b": "2"}) + .then(function () { + return context.jio.allDocs({query: 'a: "3"'}); + }) + .then(function (result) { + equal(result.data.total_rows, 1); + deepEqual(result.data.rows, [{"id": "32", "value": {}}]); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + test("Simple query matching multiple objects", function () { + var context = this; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(5); + + DummyStorage3.prototype.put = function (id, value) { + if (id === "32") { + deepEqual(value, {a: "3", b: "1"}); + } + if (id === "21") { + deepEqual(value, {a: "8", b: "1"}); + } + if (id === "3") { + deepEqual(value, {a: "5", b: "1"}); + } + return id; + }; + + RSVP.all([ + context.jio.put("32", {a: "3", b: "1"}), + context.jio.put("21", {a: "8", b: "1"}), + context.jio.put("3", {a: "5", b: "1"}) + ]) + .then(function () { + return context.jio.allDocs({query: 'b: "1"'}); + }) + .then(function (result) { + equal(result.data.total_rows, 3); + deepEqual(result.data.rows.sort(idCompare), + [ + {"id": "32", "value": {}}, + {"id": "21", "value": {}}, + {"id": "3", "value": {}} + ].sort(idCompare)); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + test("Querying with key without an index", function () { + var context = this; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(4); + + DummyStorage3.prototype.put = function (id, value) { + equal(id, "32"); + deepEqual(value, {"a": "3", "b": "2"}); + return id; + }; + + context.jio.put("32", {"a": "3", "b": "2"}) + .then(function () { + return context.jio.allDocs({query: 'b:"2"'}); + }) + .fail(function (error) { + equal(error.status_code, 501); + equal(error.message, + "Capacity 'query' is not implemented on 'dummystorage3'"); + }) + .always(function () { + start(); + }); + }); + + test("Use existing data to build Index", function () { + var context = this, fake_data; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "c"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(2); + + fake_data = { + "1": {a: "id54", b: 2, c: "night"}, + "4": {a: "vn92", b: 7, c: "matter"}, + "9": {a: "ru23", b: 3, c: "control"} + }; + + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "list") || (name === "select"); + }; + DummyStorage3.prototype.put = function (id, value) { + fake_data[id] = value; + return id; + }; + DummyStorage3.prototype.get = function (id) { + return fake_data[id]; + }; + DummyStorage3.prototype.buildQuery = function () { + var keys = Object.keys(fake_data); + return keys.map(function (v) { return {id: v, value: {id: v}}; }); + }; + + RSVP.all([ + context.jio.put("3", {a: "ab43", b: 9, c: "absorb"}), + context.jio.put("5", {a: "gu31", b: 5, c: "control"}), + context.jio.put("7", {a: "zf76", b: 3, c: "rules"}), + context.jio.put("6", {a: "cc92", b: 6, c: "afraid"}) + ]) + .then(function () { + return context.jio.allDocs({query: 'c:"control"'}); + }) + .then(function (result) { + equal(result.data.total_rows, 2); + deepEqual(result.data.rows.sort(idCompare), [{id: "5", value: {}}, + {id: "9", value: {}}]); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + test("No index keys provided but substorage supports querying", function () { + var context = this; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: [], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(3); + + DummyStorage3.prototype.put = function (id) { + return id; + }; + DummyStorage3.prototype.hasCapacity = function (capacity) { + return (capacity === "list") || (capacity === "query"); + }; + DummyStorage3.prototype.buildQuery = function (options) { + equal(options.query, 'a: "5"'); + return [{id: "3", value: {}}]; + }; + + RSVP.all([ + context.jio.put("32", {a: "3", b: "1"}), + context.jio.put("21", {a: "8", b: "1"}), + context.jio.put("3", {a: "5", b: "1"}) + ]) + .then(function () { + return context.jio.allDocs({query: 'a: "5"'}); + }) + .then(function (result) { + equal(result.data.total_rows, 1); + deepEqual(result.data.rows, [{"id": "3", "value": {}}]); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + test("Limit with query", function () { + var context = this; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(1); + + DummyStorage3.prototype.put = function (id) { + return id; + }; + + RSVP.all([ + context.jio.put("1", {"a": "55", "b": "2"}), + context.jio.put("2", {"a": "98", "b": "2"}), + context.jio.put("3", {"a": "75", "b": "2"}), + context.jio.put("8", {"a": "43", "b": "3"}), + context.jio.put("6", {"a": "21", "b": "2"}), + context.jio.put("16", {"a": "39", "b": "2"}), + context.jio.put("11", {"a": "16", "b": "2"}) + ]) + .then(function () { + return context.jio.allDocs({limit: [1, 3], query: "b:2"}); + }) + .then(function (result) { + equal(result.data.total_rows, 2); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + test("Index keys are modified", function () { + var context = this, dummy_data; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + version: 1, + index_keys: ["a"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(8); + + dummy_data = {}; + + DummyStorage3.prototype.put = function (id, value) { + dummy_data[id] = {id: id, doc: value, value: value}; + return id; + }; + DummyStorage3.prototype.get = function (id) { + if (dummy_data[id]) { + return dummy_data[id].doc; + } + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + }; + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "list") || (name === "include") || (name === "select"); + }; + DummyStorage3.prototype.buildQuery = function () { + return Object.values(dummy_data); + }; + + RSVP.all([ + context.jio.put("32", {"a": "3", "b": "2", "c": "inverse"}), + context.jio.put("5", {"a": "6", "b": "2", "c": "strong"}), + context.jio.put("14", {"a": "67", "b": "3", "c": "disolve"}) + ]) + .then(function () { + return context.jio.allDocs({query: 'a: "67"'}); + }) + .then(function (result) { + deepEqual(result.data.rows, [{"id": "14", "value": {}}]); + }) + .then(function () { + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + version: 2, + index_keys: ["a", "b", "c"], + sub_storage: { + type: "dummystorage3" + } + }); + }) + .then(function () { + return RSVP.all([ + context.jio.put("18", {"a": "2", "b": "3", "c": "align"}), + context.jio.put("62", {"a": "3", "b": "2", "c": "disolve"}) + ]); + }) + .then(function () { + return context.jio.allDocs({query: 'b: "3"'}); + }) + .then(function (result) { + deepEqual(result.data.rows, [{"id": "14", "value": {}}, + {"id": "18", "value": {}}]); + }) + .then(function () { + return context.jio.allDocs({query: 'c: "disolve"'}); + }) + .then(function (result) { + deepEqual(result.data.rows, [{"id": "14", "value": {}}, + {"id": "62", "value": {}}]); + }) + .then(function () { + return context.jio.allDocs({query: 'a: "3"'}); + }) + .then(function (result) { + deepEqual(result.data.rows, [{"id": "32", "value": {}}, + {"id": "62", "value": {}}]); + }) + .then(function () { + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "c"], + version: 3, + sub_storage: { + type: "dummystorage3" + } + }); + }) + .then(function () { + return context.jio.put("192", {"a": "3", "b": "3", "c": "disolve"}); + }) + .then(function () { + return context.jio.allDocs({query: 'a: "3"'}); + }) + .then(function (result) { + deepEqual(result.data.rows.sort(idCompare), [{"id": "192", "value": {}}, + {"id": "32", "value": {}}, {"id": "62", "value": {}}]); + }) + .then(function () { + return context.jio.allDocs({query: 'c: "disolve"'}); + }) + .then(function (result) { + deepEqual(result.data.rows.sort(idCompare), [{"id": "14", "value": {}}, + {"id": "192", "value": {}}, {"id": "62", "value": {}}]); + }) + .then(function () { + return context.jio.allDocs({query: 'b: "3"'}); + }) + .fail(function (error) { + equal(error.status_code, 501); + equal(error.message, + "Capacity 'query' is not implemented on 'dummystorage3'"); + }) + .always(function () { + start(); + }); + }); + + test("Manual repair", function () { + var context = this, fake_data; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "c"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(15); + + fake_data = { + "1": {a: "id54", b: "9", c: "night"}, + "4": {a: "vn92", b: "7", c: "matter"}, + "9": {a: "ru23", b: "3", c: "control"}, + "42": {a: "k422", b: "100", c: "grape"} + }; + + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "list") || (name === "select"); + }; + DummyStorage3.prototype.put = function (id, value) { + fake_data[id] = value; + return id; + }; + DummyStorage3.prototype.get = function (id) { + if (fake_data[id]) { + return fake_data[id]; + } + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + }; + DummyStorage3.prototype.remove = function (id) { + delete fake_data[id]; + return id; + }; + DummyStorage3.prototype.buildQuery = function () { + var keys = Object.keys(fake_data); + return keys.map(function (v) { return {id: v, value: {}}; }); + }; + + context.jio.allDocs({query: 'c: "control"'}) + .then(function (result) { + equal(result.data.total_rows, 1); + deepEqual(result.data.rows, [{id: "9", value: {}}]); + }) + .then(function () { + fake_data["2"] = {a: "zu64", b: "1", c: "matter"}; + fake_data["13"] = {a: "tk32", b: "9", c: "matter"}; + return context.jio.repair(); + }) + .then(function () { + return context.jio.allDocs({query: 'c: "matter"'}); + }) + .then(function (result) { + equal(result.data.total_rows, 3); + deepEqual(result.data.rows.sort(idCompare), [{id: "13", value: {}}, + {id: "2", value: {}}, {id: "4", value: {}}]); + }) + .then(function () { + fake_data["2"] = {a: "zu64", b: "1", c: "observe"}; + return context.jio.repair(); + }) + .then(function () { + return context.jio.allDocs({query: 'c: "observe"'}); + }) + .then(function (result) { + equal(result.data.total_rows, 1); + deepEqual(result.data.rows, [{id: "2", value: {}}]); + }) + .then(function () { + delete fake_data["2"]; + return context.jio.repair(); + }) + .then(function () { + return context.jio.allDocs({query: 'c: "observe"'}); + }) + .then(function (result) { + equal(result.data.total_rows, 0); + deepEqual(result.data.rows, []); + }) + .then(function () { + return RSVP.all([ + context.jio.put("43", {a: "t345", b: "101", c: "pear"}), + context.jio.put("44", {a: "j939", b: "121", c: "grape"}), + context.jio.put("45", {a: "q423", b: "131", c: "grape"}), + context.jio.remove("42") + ]); + }) + .then(function () { + return context.jio.repair(); + }) + .then(function () { + return context.jio.allDocs({query: "c:grape"}); + }) + .then(function (result) { + equal(result.data.total_rows, 2); + deepEqual(result.data.rows.sort(idCompare), [{id: "44", value: {}}, + {id: "45", value: {}}]); + }) + .then(function () { + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "c"], + sub_storage: { + type: "dummystorage3" + } + }); + delete fake_data["13"]; + fake_data["3"] = {a: "gg38", b: "4", c: "matter"}; + fake_data["9"] = {a: "tk32", b: "9", c: "matter"}; + return context.jio.repair(); + }) + .then(function () { + return context.jio.allDocs({query: "c:matter"}); + }) + .then(function (result) { + equal(result.data.total_rows, 3); + deepEqual(result.data.rows.sort(idCompare), [{id: "3", value: {}}, + {id: "4", value: {}}, {id: "9", value: {}}]); + }) + .then(function () { + delete fake_data["9"]; + fake_data["3"] = {a: "xu76", b: "9", c: "night"}; + fake_data["7"] = {a: "bn02", b: "9", c: "matter"}; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["b"], + version: 2, + sub_storage: { + type: "dummystorage3" + } + }); + return context.jio.allDocs({query: "b:9"}); + }) + .then(function (result) { + equal(result.data.total_rows, 3); + deepEqual(result.data.rows.sort(idCompare), [{id: "1", value: {}}, + {id: "3", value: {}}, {id: "7", value: {}}]); + }) + .then(function () { + return context.jio.allDocs({query: "c:matter"}); + }) + .fail(function (error) { + equal(error.message, + "Capacity 'query' is not implemented on 'dummystorage3'"); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // IndexStorage2.getAttachment + ///////////////////////////////////////////////////////////////// + module("IndexStorage2.getAttachment", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("getAttachment called substorage getAttachment", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["name", "user"], + sub_storage: { + type: "dummystorage3" + }, + }), + blob = new Blob([""]); + + DummyStorage3.prototype.getAttachment = function (id, name) { + equal(id, "1"); + equal(name, "test_name"); + return blob; + }; + + jio.getAttachment("1", "test_name") + .then(function (result) { + equal(result, blob); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // IndexStorage2.putAttachment + ///////////////////////////////////////////////////////////////// + module("IndexStorage2.putAttachment", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("putAttachment called substorage putAttachment", function () { + stop(); + expect(4); + + var jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["name", "user"], + sub_storage: { + type: "dummystorage3" + }, + }), + blob = new Blob([""]); + + DummyStorage3.prototype.putAttachment = function (id, name, blob2) { + equal(id, "1"); + equal(name, "test_name"); + deepEqual(blob2, blob); + return "OK"; + }; + + jio.putAttachment("1", "test_name", blob) + .then(function (result) { + equal(result, "OK"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // IndexStorage2.removeAttachment + ///////////////////////////////////////////////////////////////// + module("IndexStorage2.removeAttachment", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("removeAttachment called substorage removeAttachment", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["name", "user"], + sub_storage: { + type: "dummystorage3" + }, + }); + + DummyStorage3.prototype.removeAttachment = function (id, name) { + equal(id, "1"); + equal(name, "test_name"); + return "removed"; + }; + + jio.removeAttachment("1", "test_name") + .then(function (result) { + equal(result, "removed"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // indexStorage2.put + ///////////////////////////////////////////////////////////////// + module("indexStorage2.put", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("Put creates index", function () { + var context = this, request, store, records; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(12); + + DummyStorage3.prototype.put = function (id) { + return id; + }; + + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "list") || (name === "include") || (name === "select"); + }; + + DummyStorage3.prototype.buildQuery = function () { + return []; + }; + + RSVP.all([ + context.jio.put("32", {"a": "894", "b": "inversion", "c": 2}), + context.jio.put("33", {"a": "65", "b": "division", "c": 4}), + context.jio.put("34", {"a": "65", "b": "prolong", "c": 8}) + ]) + .then(function () { + return new RSVP.Promise(function (resolve) { + request = indexedDB.open("jio:index2_test"); + request.onsuccess = function () { + resolve(request.result); + }; + }); + }) + .then(function (result) { + equal(result.version, 1); + equal(result.name, "jio:index2_test"); + equal(result.objectStoreNames.length, 1); + equal(result.objectStoreNames[0], "index-store"); + store = result.transaction("index-store").objectStore("index-store"); + equal(store.indexNames.length, 2); + equal(store.keyPath, "id"); + deepEqual(Array.from(store.indexNames).sort(), ["Index-a", "Index-b"]); + equal(store.index("Index-a").keyPath, "doc.a"); + equal(store.index("Index-b").keyPath, "doc.b"); + equal(store.index("Index-a").unique, false); + equal(store.index("Index-b").unique, false); + return new RSVP.Promise(function (resolve) { + records = store.getAll(); + records.onsuccess = function () { + resolve(records); + }; + }); + }) + .then(function (values) { + deepEqual(values.result.sort(idCompare), [ + {"id": "32", "doc": {"a": "894", "b": "inversion"}}, + {"id": "33", "doc": {"a": "65", "b": "division"}}, + {"id": "34", "doc": {"a": "65", "b": "prolong"}} + ]); + }) + .then(function () { + request.result.close(); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // indexStorage2.post + ///////////////////////////////////////////////////////////////// + module("indexStorage2.post", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("Post creates index", function () { + var context = this, request, store, records; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(12); + + DummyStorage3.prototype.post = function (value) { + if (value.a === "5") { + return "1"; + } + if (value.a === "62") { + return "2"; + } + if (value.a === "37") { + return "3"; + } + }; + + DummyStorage3.prototype.hasCapacity = function (name) { + return (name === "list") || (name === "include") || (name === "select"); + }; + + DummyStorage3.prototype.buildQuery = function () { + return []; + }; + + RSVP.all([ + context.jio.post({"a": "5", "b": "inversion", "c": 2}), + context.jio.post({"a": "62", "b": "division", "c": 4}), + context.jio.post({"a": "37", "b": "prolong", "c": 8}) + ]) + .then(function () { + return new RSVP.Promise(function (resolve) { + request = indexedDB.open("jio:index2_test"); + request.onsuccess = function () { + resolve(request.result); + }; + }); + }) + .then(function (result) { + equal(result.version, 1); + equal(result.name, "jio:index2_test"); + equal(result.objectStoreNames.length, 1); + equal(result.objectStoreNames[0], "index-store"); + store = result.transaction("index-store").objectStore("index-store"); + equal(store.indexNames.length, 2); + equal(store.keyPath, "id"); + deepEqual(Array.from(store.indexNames).sort(), ["Index-a", "Index-b"]); + equal(store.index("Index-a").keyPath, "doc.a"); + equal(store.index("Index-b").keyPath, "doc.b"); + equal(store.index("Index-a").unique, false); + equal(store.index("Index-b").unique, false); + return new RSVP.Promise(function (resolve) { + records = store.getAll(); + records.onsuccess = function () { + resolve(records); + }; + }); + }) + .then(function (values) { + deepEqual(values.result.sort(idCompare), [ + {"id": "1", "doc": {"a": "5", "b": "inversion"}}, + {"id": "2", "doc": {"a": "62", "b": "division"}}, + {"id": "3", "doc": {"a": "37", "b": "prolong"}} + ]); + }) + .then(function () { + request.result.close(); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // indexStorage2.remove + ///////////////////////////////////////////////////////////////// + module("indexStorage2.remove", { + teardown: function () { + deleteIndexStorage2(this.jio); + } + }); + test("Remove values", function () { + var context = this, request, store, records; + context.jio = jIO.createJIO({ + type: "index2", + database: "index2_test", + index_keys: ["a", "b"], + sub_storage: { + type: "dummystorage3" + } + }); + stop(); + expect(3); + + DummyStorage3.prototype.put = function (id) { + return id; + }; + + DummyStorage3.prototype.remove = function (id) { + equal(id, "33"); + }; + + RSVP.all([ + context.jio.put("32", {"a": "894", "b": "inversion", "c": 2}), + context.jio.put("33", {"a": "65", "b": "division", "c": 4}), + context.jio.put("34", {"a": "65", "b": "prolong", "c": 8}) + ]) + .then(function () { + return new RSVP.Promise(function (resolve) { + request = indexedDB.open("jio:index2_test"); + request.onsuccess = function () { + resolve(request.result); + }; + }); + }) + .then(function (result) { + store = result.transaction("index-store").objectStore("index-store"); + return new RSVP.Promise(function (resolve) { + records = store.getAll(); + records.onsuccess = function () { + resolve(records); + }; + }); + }) + .then(function (values) { + deepEqual(values.result.sort(idCompare), [ + {"id": "32", "doc": {"a": "894", "b": "inversion"}}, + {"id": "33", "doc": {"a": "65", "b": "division"}}, + {"id": "34", "doc": {"a": "65", "b": "prolong"}} + ]); + }) + .then(function () { + return context.jio.remove("33"); + }) + .then(function () { + store = request.result.transaction("index-store") + .objectStore("index-store"); + return new RSVP.Promise(function (resolve) { + records = store.getAll(); + records.onsuccess = function () { + resolve(records); + }; + }); + }) + .then(function (values) { + deepEqual(values.result.sort(idCompare), [ + {"id": "32", "doc": {"a": "894", "b": "inversion"}}, + {"id": "34", "doc": {"a": "65", "b": "prolong"}} + ]); + }) + .then(function () { + request.result.close(); + }) + .fail(function (error) { + console.log(error); + }) + .always(function () { + start(); + }); + }); + +}(jIO, QUnit, indexedDB, Blob)); \ No newline at end of file diff --git a/test/jio.storage/liststorage.tests.js b/test/jio.storage/liststorage.tests.js new file mode 100644 index 0000000000000000000000000000000000000000..873f9f778c2cf413e115fd81eaff60e5ede54273 --- /dev/null +++ b/test/jio.storage/liststorage.tests.js @@ -0,0 +1,432 @@ +/* + * Copyright 2019, Nexedi SA + * + * This program is free software: you can Use, Study, Modify and Redistribute + * it under the terms of the GNU General Public License version 3, or (at your + * option) any later version, as published by the Free Software Foundation. + * + * You can also Link and Combine this program with other software covered by + * the terms of any of the Free Software licenses or any of the Open Source + * Initiative approved licenses and Convey the resulting work. Corresponding + * source of such a combination shall include the source code for all other + * software used. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See COPYING file for full licensing terms. + * See https://www.nexedi.com/licensing for rationale and options. + */ +/*jslint nomen: true */ +/*global Blob*/ +(function (jIO, QUnit) { + "use strict"; + var test = QUnit.test, + stop = QUnit.stop, + expect = QUnit.expect, + deepEqual = QUnit.deepEqual, + ok = QUnit.ok, + start = QUnit.start, + equal = QUnit.equal, + module = QUnit.module; + + ///////////////////////////////////////////////////////////////// + // Custom test substorage definition + ///////////////////////////////////////////////////////////////// + function DummyStorage1() { + return this; + } + function DummyStorage2() { + return this; + } + jIO.addStorage('dummystorage1', DummyStorage1); + jIO.addStorage('dummystorage2', DummyStorage2); + + ///////////////////////////////////////////////////////////////// + // ListStorage constructor + ///////////////////////////////////////////////////////////////// + + + module("ListStorage.constructor"); + + test("create storage", function () { + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + equal(jio.__type, "list"); + equal(jio.__storage._sub_storage.__type, "dummystorage1"); + equal(jio.__storage._signature_storage.__type, "dummystorage2"); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.get + ///////////////////////////////////////////////////////////////// + + module("ListStorage.get"); + + test("get called substorage get", function () { + stop(); + expect(2); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + + DummyStorage1.prototype.get = function (id) { + equal(id, "1"); + return {"name": "test_name"}; + }; + + jio.get("1") + .then(function (result) { + deepEqual(result, {"name": "test_name"}); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.allAttachments + ///////////////////////////////////////////////////////////////// + module("ListStorage.allAttachments"); + test("allAttachments called substorage allAttachments", function () { + stop(); + expect(2); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + + DummyStorage1.prototype.allAttachments = function (id) { + equal(id, "1"); + return {attachmentname: {}}; + }; + + jio.allAttachments("1") + .then(function (result) { + deepEqual(result, { + attachmentname: {} + }); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.post + ///////////////////////////////////////////////////////////////// + module("ListStorage.post"); + test("post called substorage post", function () { + stop(); + expect(4); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + + DummyStorage1.prototype.post = function (param) { + deepEqual(param, {"name": "test_name"}); + return "posted"; + }; + DummyStorage2.prototype.put = function (id, value) { + equal(id, 'posted'); + deepEqual(value, {}); + return id; + }; + + jio.post({"name": "test_name"}) + .then(function (result) { + equal(result, "posted"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.put + ///////////////////////////////////////////////////////////////// + module("ListStorage.put"); + test("put called substorage put", function () { + stop(); + expect(5); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + DummyStorage1.prototype.put = function (id, param) { + equal(id, "1"); + deepEqual(param, {"name": "test_name"}); + return id; + }; + DummyStorage2.prototype.put = function (id, param) { + equal(id, "1"); + deepEqual(param, {}); + return id; + }; + + jio.put("1", {"name": "test_name"}) + .then(function (result) { + equal(result, "1"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.remove + ///////////////////////////////////////////////////////////////// + module("ListStorage.remove"); + test("remove called substorage remove", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + DummyStorage1.prototype.remove = function (id) { + equal(id, "1"); + return id; + }; + DummyStorage2.prototype.remove = function (id) { + equal(id, "1"); + return id; + }; + + jio.remove("1") + .then(function (result) { + equal(result, "1"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.getAttachment + ///////////////////////////////////////////////////////////////// + module("ListStorage.getAttachment"); + test("getAttachment called substorage getAttachment", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }), + blob = new Blob([""]); + + DummyStorage1.prototype.getAttachment = function (id, name) { + equal(id, "1"); + equal(name, "test_name"); + return blob; + }; + + jio.getAttachment("1", "test_name") + .then(function (result) { + equal(result, blob); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.putAttachment + ///////////////////////////////////////////////////////////////// + module("ListStorage.putAttachment"); + test("putAttachment called substorage putAttachment", function () { + stop(); + expect(4); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }), + blob = new Blob([""]); + + DummyStorage1.prototype.putAttachment = function (id, name, blob2) { + equal(id, "1"); + equal(name, "test_name"); + deepEqual(blob2, blob); + return "OK"; + }; + + jio.putAttachment("1", "test_name", blob) + .then(function (result) { + equal(result, "OK"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.removeAttachment + ///////////////////////////////////////////////////////////////// + module("ListStorage.removeAttachment"); + test("removeAttachment called substorage removeAttachment", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + + DummyStorage1.prototype.removeAttachment = function (id, name) { + equal(id, "1"); + equal(name, "test_name"); + return "removed"; + }; + + jio.removeAttachment("1", "test_name") + .then(function (result) { + equal(result, "removed"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.hasCapacity + ///////////////////////////////////////////////////////////////// + module("ListStorage.hasCapacity"); + test("list capacity is implemented", function () { + expect(2); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + + DummyStorage1.prototype.hasCapacity = function () { + return false; + }; + DummyStorage2.prototype.hasCapacity = function (capacity) { + equal(capacity, 'list'); + return true; + }; + + ok(jio.hasCapacity("list")); + }); + + ///////////////////////////////////////////////////////////////// + // ListStorage.buildQuery + ///////////////////////////////////////////////////////////////// + module("ListStorage.buildQuery"); + test("buildQuery calls substorage buildQuery", function () { + stop(); + expect(1); + + var jio = jIO.createJIO({ + type: "list", + sub_storage: { + type: "dummystorage1" + }, + signature_storage: { + type: "dummystorage2", + } + }); + + DummyStorage2.prototype.buildQuery = function () { + return [{"id": "1"}, {"id": "2"}]; + }; + + jio.buildQuery() + .then(function (result) { + deepEqual(result, [{"id": "1"}, {"id": "2"}]); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + +}(jIO, QUnit)); \ No newline at end of file diff --git a/test/jio.storage/nocapacitystorage.tests.js b/test/jio.storage/nocapacitystorage.tests.js new file mode 100644 index 0000000000000000000000000000000000000000..d942fa985c8dd2781d57a3b94d1da24851f6d61f --- /dev/null +++ b/test/jio.storage/nocapacitystorage.tests.js @@ -0,0 +1,352 @@ +/* + * Copyright 2019, Nexedi SA + * + * This program is free software: you can Use, Study, Modify and Redistribute + * it under the terms of the GNU General Public License version 3, or (at your + * option) any later version, as published by the Free Software Foundation. + * + * You can also Link and Combine this program with other software covered by + * the terms of any of the Free Software licenses or any of the Open Source + * Initiative approved licenses and Convey the resulting work. Corresponding + * source of such a combination shall include the source code for all other + * software used. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See COPYING file for full licensing terms. + * See https://www.nexedi.com/licensing for rationale and options. + */ +/*jslint nomen: true */ +/*global Blob*/ +(function (jIO, QUnit) { + "use strict"; + var test = QUnit.test, + stop = QUnit.stop, + expect = QUnit.expect, + deepEqual = QUnit.deepEqual, + ok = QUnit.ok, + start = QUnit.start, + equal = QUnit.equal, + module = QUnit.module, + throws = QUnit.throws; + + ///////////////////////////////////////////////////////////////// + // Custom test substorage definition + ///////////////////////////////////////////////////////////////// + function DummyStorage() { + return this; + } + jIO.addStorage('dummystorage', DummyStorage); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage constructor + ///////////////////////////////////////////////////////////////// + + module("NoCapacityStorage.constructor"); + + test("create storage", function () { + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + equal(jio.__type, "nocapacity"); + equal(jio.__storage._sub_storage.__type, "dummystorage"); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.get + ///////////////////////////////////////////////////////////////// + + module("NoCapacityStorage.get"); + + test("get called substorage get", function () { + stop(); + expect(2); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + + DummyStorage.prototype.get = function (id) { + equal(id, "1"); + return {"name": "test_name"}; + }; + + jio.get("1") + .then(function (result) { + deepEqual(result, { + "name": "test_name" + }); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.allAttachments + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.allAttachments"); + test("allAttachments called substorage allAttachments", function () { + stop(); + expect(2); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + + DummyStorage.prototype.allAttachments = function (id) { + equal(id, "1"); + return {attachmentname: {}}; + }; + + jio.allAttachments("1") + .then(function (result) { + deepEqual(result, { + attachmentname: {} + }); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.post + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.post"); + test("post called substorage post", function () { + stop(); + expect(2); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + + DummyStorage.prototype.post = function (param) { + deepEqual(param, {"name": "test_name"}); + return "posted"; + }; + + jio.post({"name": "test_name"}) + .then(function (result) { + equal(result, "posted"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.put + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.put"); + test("put called substorage put", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + DummyStorage.prototype.put = function (id, param) { + equal(id, "1"); + deepEqual(param, {"name": "test_name"}); + return id; + }; + + jio.put("1", {"name": "test_name"}) + .then(function (result) { + equal(result, "1"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.remove + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.remove"); + test("remove called substorage remove", function () { + stop(); + expect(2); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + DummyStorage.prototype.remove = function (id) { + deepEqual(id, "1"); + return id; + }; + + jio.remove("1") + .then(function (result) { + equal(result, "1"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.getAttachment + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.getAttachment"); + test("getAttachment called substorage getAttachment", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }), + blob = new Blob([""]); + + DummyStorage.prototype.getAttachment = function (id, name) { + equal(id, "1"); + equal(name, "test_name"); + return blob; + }; + + jio.getAttachment("1", "test_name") + .then(function (result) { + equal(result, blob); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.putAttachment + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.putAttachment"); + test("putAttachment called substorage putAttachment", function () { + stop(); + expect(4); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }), + blob = new Blob([""]); + + DummyStorage.prototype.putAttachment = function (id, name, blob2) { + equal(id, "1"); + equal(name, "test_name"); + deepEqual(blob2, blob); + return "OK"; + }; + + jio.putAttachment("1", "test_name", blob) + .then(function (result) { + equal(result, "OK"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacity.removeAttachment + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.removeAttachment"); + test("removeAttachment called substorage removeAttachment", function () { + stop(); + expect(3); + + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + + DummyStorage.prototype.removeAttachment = function (id, name) { + equal(id, "1"); + equal(name, "test_name"); + return "removed"; + }; + + jio.removeAttachment("1", "test_name") + .then(function (result) { + equal(result, "removed"); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // NoCapacityStorage.hasCapacity + ///////////////////////////////////////////////////////////////// + module("NoCapacityStorage.hasCapacity"); + test("list capacity is not implemented", function () { + DummyStorage.prototype.hasCapacity = function () { + return true; + }; + var jio = jIO.createJIO({ + type: "nocapacity", + sub_storage: { + type: "dummystorage" + } + }); + throws( + function () { + jio.hasCapacity("list"); + }, + function (error) { + equal(error.message, + "Capacity 'list' is not implemented on 'nocapacity'"); + return true; + } + ); + }); + +}(jIO, QUnit)); \ No newline at end of file diff --git a/test/node.js b/test/node.js index edc60c41d58b5b472242745b7130a5a2acab89a3..f5db7eeda5382d366169c20c90c80178ec951dba 100644 --- a/test/node.js +++ b/test/node.js @@ -64,7 +64,10 @@ 'test/jio.storage/erp5storage.tests.js', 'test/jio.storage/fbstorage.tests.js', 'test/jio.storage/gdrivestorage.tests.js', + 'test/jio.storage/indexstorage2.tests.js', + 'test/jio.storage/liststorage.tests.js', 'test/jio.storage/memorystorage.tests.js', + 'test/jio.storage/nocapacitystorage.tests.js', 'test/jio.storage/querystorage.tests.js', 'test/jio.storage/replicatestorage.tests.js', 'test/jio.storage/replicatestorage_fastrepair.tests.js', diff --git a/test/tests.html b/test/tests.html index a32229e759ab1b9049b5464254d83216a62ba970..df48945f10544ddadee6097f9c28d17a0b0f674f 100644 --- a/test/tests.html +++ b/test/tests.html @@ -77,6 +77,9 @@ See https://www.nexedi.com/licensing for rationale and options. + + +