Commit 0a7b8a91 authored by lucas.parsy's avatar lucas.parsy Committed by Romain Courteaud

WebSQLStorage: add a new WebSQL storage

Squashed commit of the following:

commit 6fc5b5bd01796efd32905f7d62516ddbba7acef7
Author: Romain Courteaud <romain@nexedi.com>
Date:   Thu Dec 10 18:32:14 2015 +0100

    Cleanup rebase

commit 62e5e10d84caf9659dd32bfc71f03b7622aea5e2
Author: Lucas Parsy <lucas.parsy@nexedi.com>
Date:   Thu Dec 10 17:31:49 2015 +0100

    added scenario for webSQL storage

commit 3aae5518b87005a236dec2b3542c0cdcc9907029
Author: Lucas Parsy <lucas.parsy@nexedi.com>
Date:   Thu Dec 10 15:44:40 2015 +0100

    removed useless table in websqlstorage
    added indexes to increase queries speed.

commit 4072ea01a4bc7bd46ca611b7bf9dbf63d591c049
Author: Romain Courteaud <romain@nexedi.com>
Date:   Wed Dec 9 17:34:35 2015 +0100

    WebSQLStorage: do not sort results

commit 6d30b4bf011e6e8389a70ff185c9a739a7a2b196
Author: Romain Courteaud <romain@nexedi.com>
Date:   Wed Dec 9 17:25:23 2015 +0100

    WebSQLStorage: naming convention

commit 0928b105f756301eaaaf7ccae08c54c798a438fe
Author: Lucas Parsy <lucas.parsy@nexedi.com>
Date:   Wed Dec 9 11:09:01 2015 +0100

    various fixes in websqlstorage.

    added error callback on db.transaction.
    refactored sqlExec to do multiples queries in one transaction.
    refactored database initialisation.
    removed index functionality from websql.
    modified tests to follow above changes.

commit 00bd1cd2c5b19583bab0a74f7bcc86a59c97d3fb
Author: Lucas Parsy <lucas.parsy@nexedi.com>
Date:   Wed Dec 2 13:24:41 2015 +0100

    corrected bugs in websqlstorage breaking rsvp queues.
    added sinon spy tests in websqlstorage test file.

commit ccb2702df2fec1bd6eddca3cc2a8bbc9c8734749
Author: Lucas Parsy <lucas.parsy@nexedi.com>
Date:   Mon Nov 30 12:15:43 2015 +0100

    deleted test modifications done to Gruntfile, Makefile and tests.html files.

commit 344720f32f4ba7e9168cf68f43400bc56ea8c988
Author: Lucas Parsy <lucas.parsy@nexedi.com>
Date:   Mon Nov 30 11:41:04 2015 +0100

    added allDocs method in websqlStorage
    added tests for Websqlstorage and refactored some tests.

commit 2211a28605aedfb0683da5b3ea08f1cba47dc869
Author: Lucas Parsy <lucas.parsy@nexedi.com>
Date:   Mon Nov 23 16:29:36 2015 +0100

    first commit websql
    websql storage operational, lacks allDocs and query support
    half of the tests written.
    error handling to improve.
parent e6c460b4
......@@ -171,6 +171,7 @@ module.exports = function (grunt) {
'src/jio.storage/localstorage.js',
'src/jio.storage/zipstorage.js',
'src/jio.storage/cryptstorage.js',
'src/jio.storage/websqlstorage.js',
'src/jio.storage/dropboxstorage.js',
'src/jio.storage/davstorage.js',
'src/jio.storage/gdrivestorage.js',
......
......@@ -46,6 +46,7 @@ zip:
@cp src/jio.storage/gdrivestorage.js $(TMPDIR)/jio/storage/
@cp src/jio.storage/revisionstorage.js $(TMPDIR)/jio/storage/
@cp src/jio.storage/zipstorage.js $(TMPDIR)/jio/storage/
@cp src/jio.storage/websqlstorage.js $(TMPDIR)/jio/storage/
@cp src/jio.storage/replicaterevisionstorage.js $(TMPDIR)/jio/storage/
@cp src/jio.storage/s3storage.js $(TMPDIR)/jio/storage/
@cp src/jio.storage/splitstorage.js $(TMPDIR)/jio/storage/
......@@ -71,6 +72,7 @@ zip:
@$(UGLIFY) src/jio.storage/localstorage.js >$(TMPDIR)/jio/storage/localstorage.min.js 2>/dev/null
@$(UGLIFY) src/jio.storage/davstorage.js >$(TMPDIR)/jio/storage/davstorage.min.js 2>/dev/null
@$(UGLIFY) src/jio.storage/dropboxstorage.js >$(TMPDIR)/jio/storage/dropboxstorage.min.js 2>/dev/null
@$(UGLIFY) src/jio.storage/websqlstorage.js >$(TMPDIR)/jio/storage/websqlstorage.min.js 2>/dev/null
@$(UGLIFY) src/jio.storage/erp5storage.js >$(TMPDIR)/jio/storage/erp5storage.min.js 2>/dev/null
@$(UGLIFY) src/jio.storage/indexstorage.js >$(TMPDIR)/jio/storage/indexstorage.min.js 2>/dev/null
@$(UGLIFY) src/jio.storage/gidstorage.js >$(TMPDIR)/jio/storage/gidstorage.min.js 2>/dev/null
......
......@@ -50,6 +50,20 @@
}
});
///////////////////////////
// WebSQL storage
///////////////////////////
// return g.run({
// type: "query",
// sub_storage: {
// type: "uuid",
// sub_storage: {
// "type": "websql",
// "database": "test"
// }
// }
// });
///////////////////////////
// IndexedDB storage
///////////////////////////
......
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/**
* JIO Websql Storage. Type = "websql".
* websql "database" storage.
*/
/*global Blob, jIO, RSVP, openDatabase*/
/*jslint nomen: true*/
(function (jIO, RSVP, Blob, openDatabase) {
"use strict";
/**
* The JIO Websql Storage extension
*
* @class WebSQLStorage
* @constructor
*/
function queueSql(db, query_list, argument_list) {
return new RSVP.Promise(function (resolve, reject) {
/*jslint unparam: true*/
db.transaction(function (tx) {
var len = query_list.length,
result_list = [],
i;
function resolveTransaction(tx, result) {
result_list.push(result);
if (result_list.length === len) {
resolve(result_list);
}
}
function rejectTransaction(tx, error) {
reject(error);
return true;
}
for (i = 0; i < len; i += 1) {
tx.executeSql(query_list[i], argument_list[i], resolveTransaction,
rejectTransaction);
}
}, function (tx, error) {
reject(error);
});
/*jslint unparam: false*/
});
}
function initDatabase(db) {
var query_list = [
"CREATE TABLE IF NOT EXISTS document" +
"(id VARCHAR PRIMARY KEY NOT NULL, data TEXT)",
"CREATE TABLE IF NOT EXISTS attachment" +
"(id VARCHAR, attachment VARCHAR, part INT, blob TEXT)",
"CREATE TRIGGER IF NOT EXISTS removeAttachment " +
"BEFORE DELETE ON document FOR EACH ROW " +
"BEGIN DELETE from attachment WHERE id = OLD.id;END;",
"CREATE INDEX IF NOT EXISTS index_document ON document (id);",
"CREATE INDEX IF NOT EXISTS index_attachment " +
"ON attachment (id, attachment);"
];
return new RSVP.Queue()
.push(function () {
return queueSql(db, query_list, []);
});
}
function WebSQLStorage(spec) {
if (typeof spec.database !== 'string' || !spec.database) {
throw new TypeError("database must be a string " +
"which contains more than one character.");
}
this._database = openDatabase("jio:" + spec.database,
'1.0', '', 2 * 1024 * 1024);
if (spec.blob_length &&
(typeof spec.blob_length !== "number" ||
spec.blob_length < 20)) {
throw new TypeError("blob_len parameter must be a number >= 20");
}
this._blob_length = spec.blob_length || 2000000;
this._init_db_promise = initDatabase(this._database);
}
WebSQLStorage.prototype.put = function (id, param) {
var db = this._database,
that = this,
data_string = JSON.stringify(param);
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["INSERT OR REPLACE INTO " +
"document(id, data) VALUES(?,?)"],
[[id, data_string]]);
})
.push(function () {
return id;
});
};
WebSQLStorage.prototype.remove = function (id) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["DELETE FROM document WHERE id = ?"], [[id]]);
})
.push(function (result_list) {
if (result_list[0].rowsAffected === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
return id;
});
};
WebSQLStorage.prototype.get = function (id) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["SELECT data FROM document WHERE id = ?"],
[[id]]);
})
.push(function (result_list) {
if (result_list[0].rows.length === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
return JSON.parse(result_list[0].rows[0].data);
});
};
WebSQLStorage.prototype.allAttachments = function (id) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, [
"SELECT id FROM document WHERE id = ?",
"SELECT DISTINCT attachment FROM attachment WHERE id = ?"
], [[id], [id]]);
})
.push(function (result_list) {
if (result_list[0].rows.length === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
var len = result_list[1].rows.length,
obj = {},
i;
for (i = 0; i < len; i += 1) {
obj[result_list[1].rows[i].attachment] = {};
}
return obj;
});
};
function sendBlobPart(blob, argument_list, index, queue) {
queue.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (strBlob) {
argument_list[index + 2].push(strBlob.currentTarget.result);
return;
});
}
WebSQLStorage.prototype.putAttachment = function (id, name, blob) {
var db = this._database,
that = this,
part_size = this._blob_length;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["SELECT id FROM document WHERE id = ?"], [[id]]);
})
.push(function (result) {
var query_list = [],
argument_list = [],
blob_size = blob.size,
queue = new RSVP.Queue(),
i,
index;
if (result[0].rows.length === 0) {
throw new jIO.util.jIOError("Cannot access subdocument", 404);
}
query_list.push("DELETE FROM attachment WHERE id = ? " +
"AND attachment = ?");
argument_list.push([id, name]);
query_list.push("INSERT INTO attachment(id, attachment, part, blob)" +
"VALUES(?, ?, ?, ?)");
argument_list.push([id, name, -1,
blob.type || "application/octet-stream"]);
for (i = 0, index = 0; i < blob_size; i += part_size, index += 1) {
query_list.push("INSERT INTO attachment(id, attachment, part, blob)" +
"VALUES(?, ?, ?, ?)");
argument_list.push([id, name, index]);
sendBlobPart(blob.slice(i, i + part_size), argument_list, index,
queue);
}
queue.push(function () {
return queueSql(db, query_list, argument_list);
});
return queue;
});
};
WebSQLStorage.prototype.getAttachment = function (id, name, options) {
var db = this._database,
that = this,
part_size = this._blob_length,
start,
end,
start_index,
end_index;
if (options === undefined) { options = {}; }
start = options.start || 0;
end = options.end || -1;
if (start < 0 || (options.end !== undefined && options.end < 0)) {
throw new jIO.util.jIOError("_start and _end must be positive",
400);
}
if (start > end && end !== -1) {
throw new jIO.util.jIOError("_start is greater than _end",
400);
}
start_index = Math.floor(start / part_size);
if (start === 0) { start_index -= 1; }
end_index = Math.floor(end / part_size);
if (end % part_size === 0) {
end_index -= 1;
}
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
var command = "SELECT part, blob FROM attachment WHERE id = ? AND " +
"attachment = ? AND part >= ?",
argument_list = [id, name, start_index];
if (end !== -1) {
command += " AND part <= ?";
argument_list.push(end_index);
}
return queueSql(db, [command], [argument_list]);
})
.push(function (response_list) {
var i,
response,
blob_array = [],
blob,
type;
response = response_list[0].rows;
if (response.length === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
for (i = 0; i < response.length; i += 1) {
if (response[i].part === -1) {
type = response[i].blob;
start_index += 1;
} else {
blob_array.push(jIO.util.dataURItoBlob(response[i].blob));
}
}
if ((start === 0) && (options.end === undefined)) {
return new Blob(blob_array, {type: type});
}
blob = new Blob(blob_array, {});
return blob.slice(start - (start_index * part_size),
end === -1 ? blob.size :
end - (start_index * part_size),
"application/octet-stream");
});
};
WebSQLStorage.prototype.removeAttachment = function (id, name) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["DELETE FROM attachment WHERE " +
"id = ? AND attachment = ?"], [[id, name]]);
})
.push(function (result) {
if (result[0].rowsAffected === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
return name;
});
};
WebSQLStorage.prototype.hasCapacity = function (name) {
return (name === "list" || (name === "include"));
};
WebSQLStorage.prototype.buildQuery = function (options) {
var db = this._database,
that = this,
query = "SELECT id";
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
if (options === undefined) { options = {}; }
if (options.include_docs === true) {
query += ", data AS doc";
}
query += " FROM document";
return queueSql(db, [query], [[]]);
})
.push(function (result) {
var array = [],
len = result[0].rows.length,
i;
for (i = 0; i < len; i += 1) {
array.push(result[0].rows[i]);
array[i].value = {};
if (array[i].doc !== undefined) {
array[i].doc = JSON.parse(array[i].doc);
}
}
return array;
});
};
jIO.addStorage('websql', WebSQLStorage);
}(jIO, RSVP, Blob, openDatabase));
/*jslint nomen: true */
/*global openDatabase, Blob, sinon*/
(function (jIO, QUnit, openDatabase, Blob, sinon) {
"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,
big_string = "",
db = openDatabase('jio:qunit', '1.0', '', 2 * 1024 * 1024),
j;
function getSpy() {
return new RSVP.Promise(function (resolve, reject) {
var spy;
db.transaction(function (tx) {
spy = sinon.spy(tx.constructor.prototype, "executeSql");
resolve(spy);
}, reject);
});
}
function exec(transac, args) {
return new RSVP.Promise(function (resolve, reject) {
db.transaction(function (tx) {
/*jslint unparam: true*/
tx.executeSql(transac, args,
function (tx, result) {
resolve(result);
},
function (tx, error) {
reject(error);
});
});
/*jslint unparam: false*/
});
}
function spyStorageCreation(context) {
ok(context.spy.callCount >= 5);
equal(context.spy.args[0][0],
'CREATE TABLE IF NOT EXISTS document(id VARCHAR PRIMARY ' +
'KEY NOT NULL, data TEXT)');
equal(context.spy.args[1][0],
'CREATE TABLE IF NOT EXISTS attachment(id VARCHAR, attachment' +
' VARCHAR, part INT, blob TEXT)');
equal(context.spy.args[2][0],
"CREATE TRIGGER IF NOT EXISTS removeAttachment " +
"BEFORE DELETE ON document FOR EACH ROW " +
"BEGIN DELETE from attachment WHERE id = OLD.id;END;");
equal(context.spy.args[3][0],
"CREATE INDEX IF NOT EXISTS index_document ON document (id);");
equal(context.spy.args[4][0], "CREATE INDEX IF NOT EXISTS " +
"index_attachment ON attachment (id, attachment);");
}
function deleteRow(it, queue, result) {
queue.push(function () {
return exec("DELETE FROM " + result.rows[it].name);
});
}
function deleteWebsql() {
return new RSVP.Queue()
.push(function () {
return exec('SELECT name FROM sqlite_master WHERE' +
' type ="table" AND name != "__WebKitDatabaseInfoTable__"');
})
.push(function (result) {
var i,
len = result.rows.length,
queue = new RSVP.Queue();
for (i = 0; i < len; i += 1) {
deleteRow(i, queue, result);
}
return queue;
});
}
for (j = 0; j < 40; j += 1) {
big_string += "a";
}
/////////////////////////////////////////////////////////////////
// websqlStorage.constructor
/////////////////////////////////////////////////////////////////
module("websqlStorage.constructor", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("creation of the storage", function () {
var context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
equal(context.jio.__type, "websql");
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.hasCapacity
/////////////////////////////////////////////////////////////////
module("websqlStorage.hasCapacity", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("can list document", function () {
var context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
ok(context.jio.hasCapacity("list"));
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.buildQuery
/////////////////////////////////////////////////////////////////
module("websqlStorage.buildQuery", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage", function () {
var context = this;
stop();
expect(8);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.allDocs();
})
.then(function () {
equal(context.spy.args[5][0], 'SELECT id FROM document');
deepEqual(context.spy.args[5][1], []);
spyStorageCreation(context);
return;
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("spy webSQL usage with include_docs", function () {
var context = this;
stop();
expect(8);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.allDocs({include_docs: true});
})
.then(function () {
equal(context.spy.args[5][0],
'SELECT id, data AS doc FROM document');
deepEqual(context.spy.args[5][1], []);
spyStorageCreation(context);
return;
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("empty result", function () {
var context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
})
.then(function () {
return context.jio.allDocs();
})
.then(function (result) {
deepEqual(result, {
"data": {
"rows": [
],
"total_rows": 0
}
});
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("list all document", function () {
var context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
})
.then(function () {
return RSVP.all([
context.jio.put("2", {"title": "title2"}),
context.jio.put("1", {"title": "title1"})
]);
})
.then(function () {
return context.jio.allDocs();
})
.then(function (result) {
deepEqual(result, {
"data": {
"rows": [{
"id": "1",
"value": {}
}, {
"id": "2",
"value": {}
}],
"total_rows": 2
}
});
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("handle include_docs", function () {
var context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
})
.then(function () {
return RSVP.all([
context.jio.put("2", {"title": "title2"}),
context.jio.put("1", {"title": "title1"})
]);
})
.then(function () {
return context.jio.allDocs({include_docs: true});
})
.then(function (result) {
deepEqual(result, {
"data": {
"rows": [{
"id": "2",
"doc": {"title": "title2"},
"value": {}
}, {
"id": "1",
"doc": {"title": "title1"},
"value": {}
}],
"total_rows": 2
}
});
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.get
/////////////////////////////////////////////////////////////////
module("websqlStorage.get", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage", function () {
var context = this;
stop();
expect(10);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.get("foo");
})
.then(function () {
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1], ['foo', '{"title":"bar"}']);
equal(context.spy.args[6][0],
'SELECT data FROM document WHERE id = ?');
deepEqual(context.spy.args[6][1], ['foo']);
spyStorageCreation(context);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("get inexistent document", function () {
var context = this;
stop();
expect(3);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.get("inexistent");
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document");
equal(error.status_code, 404);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("get document without attachment", function () {
var id = "/",
context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put(id, {"title": "bar"});
})
.then(function () {
return context.jio.get(id);
})
.then(function (result) {
deepEqual(result, {
"title": "bar"
}, "Check document");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("get document with attachment", function () {
var id = "/",
attachment = "foo",
context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put(id, {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment(id, attachment, "bar");
})
.then(function () {
return context.jio.get(id);
})
.then(function (result) {
deepEqual(result, {
"title": "bar"
}, "Check document");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.allAttachments
/////////////////////////////////////////////////////////////////
module("websqlStorage.allAttachments", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage", function () {
var context = this;
stop();
expect(12);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.allAttachments("foo");
})
.then(function () {
spyStorageCreation(context);
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1], ['foo', '{"title":"bar"}']);
equal(context.spy.args[6][0],
'SELECT id FROM document WHERE id = ?');
deepEqual(context.spy.args[6][1], ['foo']);
equal(context.spy.args[7][0],
'SELECT DISTINCT attachment FROM attachment WHERE id = ?');
deepEqual(context.spy.args[7][1], ['foo']);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("get inexistent document", function () {
var context = this;
stop();
expect(3);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.allAttachments("inexistent");
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document");
equal(error.status_code, 404);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("get document without attachment", function () {
var id = "/",
context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put(id, {"title": "bar"});
})
.then(function () {
return context.jio.allAttachments(id);
})
.then(function (result) {
deepEqual(result, {}, "Check document");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("get document with attachment", function () {
var id = "/",
attachment = "foo",
context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put(id, {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment(id, attachment, "bar");
})
.then(function () {
return context.jio.allAttachments(id);
})
.then(function (result) {
deepEqual(result, {
"foo": {}
}, "Check document");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.put
/////////////////////////////////////////////////////////////////
module("websqlStorage.put", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage", function () {
var context = this;
stop();
expect(8);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.allAttachments("foo");
})
.then(function () {
spyStorageCreation(context);
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1], ['foo', '{"title":"bar"}']);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("put document", function () {
var context = this;
stop();
expect(1);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put("inexistent", {});
})
.then(function (result) {
equal(result, "inexistent");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.remove
/////////////////////////////////////////////////////////////////
module("websqlStorage.remove", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage with one document", function () {
var context = this;
stop();
expect(10);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.remove("foo");
})
.then(function () {
spyStorageCreation(context);
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1], ['foo', '{"title":"bar"}']);
equal(context.spy.args[6][0], 'DELETE FROM document WHERE id = ?');
deepEqual(context.spy.args[6][1], ['foo']);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("spy webSQL usage with two attachments", function () {
var context = this;
stop();
expect(26);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", "attachment1", "bar");
})
.then(function () {
return context.jio.putAttachment("foo", "attachment2", "bar2");
})
.then(function () {
return context.jio.remove("foo");
})
.then(function () {
spyStorageCreation(context);
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1], ['foo', '{"title":"bar"}']);
equal(context.spy.args[6][0],
'SELECT id FROM document WHERE id = ?');
deepEqual(context.spy.args[6][1], ['foo']);
equal(context.spy.args[7][0],
'DELETE FROM attachment WHERE id = ? AND attachment = ?');
deepEqual(context.spy.args[7][1], ['foo', 'attachment1']);
equal(context.spy.args[8][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[8][1],
['foo', 'attachment1', -1, 'text/plain;charset=utf-8']);
equal(context.spy.args[9][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[9][1],
['foo', 'attachment1', 0, 'data:;base64,YmFy']);
equal(context.spy.args[10][0],
'SELECT id FROM document WHERE id = ?');
deepEqual(context.spy.args[10][1], ['foo']);
equal(context.spy.args[11][0],
'DELETE FROM attachment WHERE id = ? AND attachment = ?');
deepEqual(context.spy.args[11][1], ['foo', 'attachment2']);
equal(context.spy.args[12][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[12][1],
['foo', 'attachment2', -1, 'text/plain;charset=utf-8']);
equal(context.spy.args[13][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[13][1],
['foo', 'attachment2', 0, 'data:;base64,YmFyMg==']);
equal(context.spy.args[14][0], 'DELETE FROM document WHERE id = ?');
deepEqual(context.spy.args[14][1], ['foo']);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("remove document", function () {
var context = this;
stop();
expect(3);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put("foo", {});
})
.then(function () {
return exec("SELECT id FROM document", []);
})
.then(function (selectResult) {
equal(selectResult.rows.length, 1, "putAttachment done");
})
.then(function () {
return context.jio.remove("foo");
})
.then(function (result) {
equal(result, "foo");
return exec("SELECT id FROM document", []);
})
.then(function (selectResult) {
equal(selectResult.rows.length, 0, "remove done");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.getAttachment
/////////////////////////////////////////////////////////////////
module("websqlStorage.getAttachment", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit",
blob_length: 20
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage", function () {
var context = this;
stop();
expect(20);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", "attachment", big_string);
})
.then(function () {
return context.jio.getAttachment("foo", "attachment");
})
.then(function () {
spyStorageCreation(context);
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1],
['foo', '{"title":"bar"}']);
equal(context.spy.args[6][0],
'SELECT id FROM document WHERE id = ?');
deepEqual(context.spy.args[6][1], ['foo']);
equal(context.spy.args[7][0],
'DELETE FROM attachment WHERE id = ? AND attachment = ?');
deepEqual(context.spy.args[7][1], ['foo', 'attachment']);
equal(context.spy.args[8][0],
'INSERT INTO attachment(id, attachment, part, blob)' +
'VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[8][1],
['foo', 'attachment', -1, 'text/plain;charset=utf-8']);
equal(context.spy.args[9][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[9][1],
['foo', 'attachment', 0,
'data:;base64,YWFhYWFhYWFhYWFhYWFhYWFhYWE=']);
equal(context.spy.args[10][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[10][1],
['foo', 'attachment', 1,
'data:;base64,YWFhYWFhYWFhYWFhYWFhYWFhYWE=']);
equal(context.spy.args[11][0],
'SELECT part, blob FROM attachment WHERE id = ? ' +
'AND attachment = ? AND part >= ?');
deepEqual(context.spy.args[11][1], ['foo', 'attachment', -1]);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("check result", function () {
var context = this,
attachment = "attachment";
stop();
expect(3);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", attachment, big_string);
})
.then(function () {
return context.jio.getAttachment("foo", attachment);
})
.then(function (result) {
ok(result instanceof Blob, "Data is Blob");
equal(result.type, "text/plain;charset=utf-8");
return jIO.util.readBlobAsText(result);
})
.then(function (result) {
equal(result.target.result, big_string,
"Attachment correctly fetched");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("streaming", function () {
var context = this,
attachment = "attachment";
stop();
expect(3);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", attachment, big_string);
})
.then(function () {
return context.jio.getAttachment("foo", attachment,
{"start": 15, "end": 25});
})
.then(function (result) {
ok(result instanceof Blob, "Data is Blob");
equal(result.type, "application/octet-stream");
return jIO.util.readBlobAsText(result);
})
.then(function (result) {
var expected = "aaaaaaaaaa";
equal(result.target.result, expected, "Attachment correctly fetched");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.removeAttachment
/////////////////////////////////////////////////////////////////
module("websqlStorage.removeAttachment", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit"
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage", function () {
var context = this;
stop();
expect(18);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", "attachment", big_string);
})
.then(function () {
return context.jio.removeAttachment("foo", "attachment");
})
.then(function () {
spyStorageCreation(context);
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1],
['foo', '{"title":"bar"}']);
equal(context.spy.args[6][0],
'SELECT id FROM document WHERE id = ?');
deepEqual(context.spy.args[6][1], ['foo']);
equal(context.spy.args[7][0],
'DELETE FROM attachment WHERE id = ? AND attachment = ?');
deepEqual(context.spy.args[7][1], ['foo', 'attachment']);
equal(context.spy.args[8][0],
'INSERT INTO attachment(id, attachment, part, blob)' +
'VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[8][1],
['foo', 'attachment', -1, 'text/plain;charset=utf-8']);
equal(context.spy.args[9][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[9][1],
['foo', 'attachment', 0,
"data:;base64,YWFhYWFhYWFhYWFhYWFhYWF" +
"hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ=="]);
equal(context.spy.args[10][0],
'DELETE FROM attachment WHERE id = ? AND attachment = ?');
deepEqual(context.spy.args[10][1], ['foo', 'attachment']);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("remove attachment", function () {
var context = this,
attachment = "attachment";
stop();
expect(3);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", attachment, big_string);
})
.then(function () {
return exec("SELECT id, attachment FROM attachment", []);
})
.then(function (selectResult) {
equal(selectResult.rows.length, 2, "putAttachment done");
})
.then(function () {
return context.jio.removeAttachment("foo", attachment);
})
.then(function (result) {
equal(result, attachment);
return exec("SELECT id, attachment FROM attachment", []);
})
.then(function (selectResult) {
equal(selectResult.rows.length, 0, "removeAttachment done");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// websqlStorage.putAttachment
/////////////////////////////////////////////////////////////////
module("websqlStorage.putAttachment", {
setup: function () {
this.jio = jIO.createJIO({
type: "websql",
database: "qunit",
blob_length: 20
});
this.spy = getSpy();
},
teardown: function () {
this.spy.restore();
delete this.spy;
}
});
test("spy webSQL usage", function () {
var context = this;
stop();
expect(18);
context.spy.then(function (value) {
context.spy = value;
return;
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", "attachment", big_string);
})
.then(function () {
spyStorageCreation(context);
equal(context.spy.args[5][0],
'INSERT OR REPLACE INTO document(id, data) VALUES(?,?)');
deepEqual(context.spy.args[5][1],
['foo', '{"title":"bar"}']);
equal(context.spy.args[6][0],
'SELECT id FROM document WHERE id = ?');
deepEqual(context.spy.args[6][1], ['foo']);
equal(context.spy.args[7][0],
'DELETE FROM attachment WHERE id = ? AND attachment = ?');
deepEqual(context.spy.args[7][1], ['foo', 'attachment']);
equal(context.spy.args[8][0],
'INSERT INTO attachment(id, attachment, part, blob)' +
'VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[8][1],
['foo', 'attachment', -1, 'text/plain;charset=utf-8']);
equal(context.spy.args[9][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[9][1],
['foo', 'attachment', 0,
'data:;base64,YWFhYWFhYWFhYWFhYWFhYWFhYWE=']);
equal(context.spy.args[10][0],
'INSERT INTO attachment(id, attachment, ' +
'part, blob)VALUES(?, ?, ?, ?)');
deepEqual(context.spy.args[10][1],
['foo', 'attachment', 1,
'data:;base64,YWFhYWFhYWFhYWFhYWFhYWFhYWE=']);
})
.then(function () {
return deleteWebsql();
})
.fail(function (error) {
ok(false, error);
return deleteWebsql();
})
.always(function () {
start();
});
});
test("put attachment", function () {
var context = this,
attachment = "attachment";
stop();
expect(2);
context.spy.then(function (value) {
context.spy = value;
return deleteWebsql();
})
.then(function () {
return context.jio.put("foo", {"title": "bar"});
})
.then(function () {
return context.jio.putAttachment("foo", attachment, big_string);
})
.then(function () {
return exec("SELECT id, attachment FROM attachment", []);
})
.then(function (selectResult) {
equal(selectResult.rows.length, 3, "putAttachment done");
})
.then(function () {
return context.jio.getAttachment("foo", attachment);
})
.then(function (result) {
return jIO.util.readBlobAsText(result);
})
.then(function (result) {
equal(result.target.result, big_string, "attachment correctly fetched");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
}(jIO, QUnit, openDatabase, Blob, sinon));
......@@ -47,6 +47,7 @@
<script src="jio.storage/dropboxstorage.tests.js"></script>
<script src="jio.storage/zipstorage.tests.js"></script>
<script src="jio.storage/gdrivestorage.tests.js"></script>
<script src="jio.storage/websqlstorage.tests.js"></script>
<!--script src="../lib/jquery/jquery.min.js"></script>
<script src="../src/jio.storage/xwikistorage.js"></script>
<script src="jio.storage/xwikistorage.tests.js"></script-->
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment