cordova-sqlcipher-adapter
Version:
SQLCipher database adapter for PhoneGap/Cordova, based on cordova-sqlite-storage
433 lines (378 loc) • 17.2 kB
JavaScript
/* 'use strict'; */
var MYTIMEOUT = 12000;
// NOTE: DEFAULT_SIZE wanted depends on type of browser
var isWindows = /MSAppHost/.test(navigator.userAgent);
var isAndroid = !isWindows && /Android/.test(navigator.userAgent);
var isFirefox = /Firefox/.test(navigator.userAgent);
var isWebKitBrowser = !isWindows && !isAndroid && /Safari/.test(navigator.userAgent);
var isBrowser = isWebKitBrowser || isFirefox;
var isEdgeBrowser = isBrowser && (/Edge/.test(navigator.userAgent));
var isChromeBrowser = isBrowser && !isEdgeBrowser && (/Chrome/.test(navigator.userAgent));
var isSafariBrowser = isWebKitBrowser && !isEdgeBrowser && !isChromeBrowser;
// should avoid popups (Safari seems to count 2x)
var DEFAULT_SIZE = isSafariBrowser ? 2000000 : 5000000;
// FUTURE TBD: 50MB should be OK on Chrome and some other test browsers.
// NOTE: While in certain version branches there is no difference between
// the default Android implementation and implementation #2,
// this test script will also apply the androidLockWorkaround: 1 option
// in case of implementation #2.
var scenarioList = [
isAndroid ? 'Plugin-implementation-default' : 'Plugin',
'HTML5',
'Plugin-implementation-2'
];
var scenarioCount = (!!window.hasWebKitWebSQL) ? (isAndroid ? 3 : 2) : 1;
function logSuccess(message) { console.log('OK - ' + message); }
function logError(message) { console.log('FAILED - ' + message); }
var mytests = function() {
for (var i=0; i<scenarioCount; ++i) {
// TBD skip plugin test on browser platform (not yet supported):
if (isBrowser && (i === 0)) continue;
describe(scenarioList[i] + ': simultaneous tx access test(s)', function() {
var scenarioName = scenarioList[i];
var suiteName = scenarioName + ': ';
var isWebSql = (i === 1);
var isImpl2 = (i === 2);
// NOTE: MUST be defined in function scope, NOT outer scope:
var openDatabase = function(name, ignored1, ignored2, ignored3) {
if (isImpl2) {
return window.sqlitePlugin.openDatabase({
// prevent reuse of database from default db implementation:
name: 'i2-'+name,
// explicit database location:
location: 'default',
androidDatabaseImplementation: 2,
androidLockWorkaround: 1
});
}
if (isWebSql) {
return window.openDatabase(name, '1.0', 'Test', DEFAULT_SIZE);
} else {
// explicit database location:
return window.sqlitePlugin.openDatabase({name: name, location: 'default'});
}
}
it(suiteName + ' open same db twice with string test', function (done) {
var dbName = 'open-same-db-twice-string-test.db';
var db1 = openDatabase(dbName, "1.0", "Demo", DEFAULT_SIZE);
var db2 = openDatabase(dbName, "1.0", "Demo", DEFAULT_SIZE);
expect(db1).toBeTruthy(); // valid db1 database handle object
expect(db2).toBeTruthy(); // valid db2 database handle object
// Replacement for QUnit stop()/start() functions:
var checkCount = 0;
var expectedCheckCount = 2;
db1.readTransaction(function(tx) {
expect(tx).toBeTruthy(); // valid db1 tx object
tx.executeSql("select upper('first') as uppertext", [], function(tx, result) {
expect(result).toBeTruthy(); // valid db1 read tx result object
expect(result.rows.item(0).uppertext).toBe('FIRST'); // check db1 read tx result
if (++checkCount === expectedCheckCount) done();
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error);
expect(error.message).toBe('--');
done.fail();
});
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error);
expect(error.message).toBe('--');
done.fail();
});
db2.readTransaction(function(tx) {
expect(tx).toBeTruthy(); // valid db2 tx object
tx.executeSql("select upper('second') as uppertext", [], function(tx, result) {
expect(result).toBeTruthy(); // valid db2 read tx result object
expect(result.rows.item(0).uppertext).toBe('SECOND'); // check db2 read tx result
if (++checkCount === expectedCheckCount) done();
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error);
expect(error.message).toBe('--');
done.fail();
});
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error);
expect(error.message).toBe('--');
done.fail();
});
});
it(suiteName + ' test simultaneous transactions (same db handle)', function (done) {
var db = openDatabase("Database-Simultaneous-Tx", "1.0", "Demo", DEFAULT_SIZE);
var numDone = 0;
function checkDone() {
if (++numDone == 2) {
done();
}
}
db.transaction(function(tx) {
tx.executeSql('DROP TABLE IF EXISTS test', [], function () {
tx.executeSql('CREATE TABLE test (name);');
});
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error);
expect(error.message).toBe('--');
done.fail();
}, function () {
db.transaction(function(tx) {
tx.executeSql('INSERT INTO test VALUES ("foo")', [], function () {
tx.executeSql('SELECT * FROM test', [], function (tx, res) {
expect(res.rows.length).toBe(1); // only one row
expect(res.rows.item(0).name).toBe('foo');
tx.executeSql('SELECT * FROM bogustable'); // force rollback
});
});
}, function(error) {
// EXPECTED RESULT - expected error:
expect(error).toBeDefined();
expect(error.message).toBeDefined();
checkDone();
}, function () {
// NOT EXPECTED - should have rolled back:
logError('should have rolled back');
expect(error.message).toBe('--');
done.fail();
});
db.transaction(function(tx) {
tx.executeSql('INSERT INTO test VALUES ("bar")', [], function () {
tx.executeSql('SELECT * FROM test', [], function (tx, res) {
expect(res.rows.length).toBe(1); // only one row
expect(res.rows.item(0).name).toBe('bar');
tx.executeSql('SELECT * FROM bogustable'); // force rollback
});
});
}, function(error) {
// EXPECTED RESULT - expected error:
expect(error).toBeDefined();
expect(error.message).toBeDefined();
checkDone();
}, function () {
// NOT EXPECTED - should have rolled back:
logError('should have rolled back');
done.fail();
});
});
});
it(suiteName + ' test simultaneous transactions, different db handles (same db)', function (done) {
var dbName = "Database-Simultaneous-Tx-Diff-DB-handles";
var db = openDatabase(dbName, "1.0", "Demo", DEFAULT_SIZE);
var numDone = 0;
function checkDone() {
if (++numDone == 2) {
done();
}
}
db.transaction(function(tx) {
tx.executeSql('DROP TABLE IF EXISTS test', [], function () {
tx.executeSql('CREATE TABLE test (name);');
});
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error);
expect(error.message).toBe('--');
done.fail();
}, function () {
var db1 = openDatabase(dbName, "1.0", "Demo", DEFAULT_SIZE);
db1.transaction(function(tx) {
tx.executeSql('INSERT INTO test VALUES ("foo")', [], function () {
tx.executeSql('SELECT * FROM test', [], function (tx, res) {
expect(res.rows.length).toBe(1); // only one row
expect(res.rows.item(0).name).toBe('foo');
tx.executeSql('SELECT * FROM bogustable'); // force rollback
});
});
}, function(error) {
// EXPECTED RESULT - expected error:
expect(error).toBeDefined();
expect(error.message).toBeDefined();
checkDone();
}, function () {
// NOT EXPECTED - should have rolled back:
logError('should have rolled back');
done.fail();
});
var db2 = openDatabase(dbName, "1.0", "Demo", DEFAULT_SIZE);
db2.transaction(function(tx) {
tx.executeSql('INSERT INTO test VALUES ("bar")', [], function () {
tx.executeSql('SELECT * FROM test', [], function (tx, res) {
expect(res.rows.length).toBe(1); // only one row
expect(res.rows.item(0).name).toBe('bar');
tx.executeSql('SELECT * FROM bogustable'); // force rollback
});
});
}, function(error) {
// EXPECTED RESULT - expected error:
expect(error).toBeDefined();
expect(error.message).toBeDefined();
checkDone();
}, function () {
// NOT EXPECTED - should have rolled back:
logError('should have rolled back');
done.fail();
});
});
});
it(suiteName + ' can open two databases at the same time', function (done) {
// create databases and tables
var db1 = openDatabase("DB1", "1.0", "Demo", DEFAULT_SIZE);
db1.transaction(function (tx1) {
tx1.executeSql('CREATE TABLE IF NOT EXISTS test1 (x int)');
});
var db2 = openDatabase("DB2", "1.0", "Demo", DEFAULT_SIZE);
db2.transaction(function (tx2) {
tx2.executeSql('CREATE TABLE IF NOT EXISTS test2 (x int)');
});
// two databases that perform two queries and one commit each, then repeat
// Quick replacement for QUnit stop()/start() functions:
var checkCount = 0;
var expectedCheckCount = 12;
// create overlapping transactions
db1.transaction(function (tx1) {
db2.transaction(function (tx2) {
tx2.executeSql('INSERT INTO test2 VALUES (2)', [], function (tx, result) {
logSuccess('inserted into second database');
++checkCount;
});
tx2.executeSql('SELECT * from test2', [], function (tx, result) {
expect(result.rows.item(0).x).toBe(2); // selected from second database
++checkCount;
});
}, function (error) {
// ERROR NOT EXPECTED here:
logError('transaction 2 failed ' + error);
expect(error.message).toBe('--');
done.fail();
}, function () {
logSuccess('transaction 2 committed');
++checkCount;
});
tx1.executeSql('INSERT INTO test1 VALUES (1)', [], function (tx, result) {
logSuccess('inserted into first database');
++checkCount;
});
tx1.executeSql('SELECT * from test1', [], function (tx, result) {
expect(result.rows.item(0).x).toBe(1); // selected from first database
++checkCount;
});
}, function (error) {
// ERROR NOT EXPECTED here:
logError('transaction 1 failed ' + error);
expect(error.message).toBe('--');
done.fail();
}, function () {
logSuccess('transaction 1 committed');
if (++checkCount === expectedCheckCount) done();
});
// now that the databases are truly open, do it again!
// - should wait for first db1.transaction() call to finish
// - must check for - checkCount === expectedCheckCount in both
// db1.transaction() callback & db2.transaction() callback
// since it is not certain which will finish first or last.
db1.transaction(function (tx1) {
db2.transaction(function (tx2) {
tx2.executeSql('INSERT INTO test2 VALUES (2)', [], function (tx, result) {
logSuccess('inserted into second database');
++checkCount;
});
tx2.executeSql('SELECT * from test2', [], function (tx, result) {
expect(result.rows.item(0).x).toBe(2); // selected from second database
++checkCount;
});
}, function (error) {
// ERROR NOT EXPECTED here:
logError('transaction 2 failed ' + error);
expect(error.message).toBe('--');
done.fail();
}, function () {
logSuccess('transaction 2 committed');
if (++checkCount === expectedCheckCount) done();
});
tx1.executeSql('INSERT INTO test1 VALUES (1)', [], function (tx, result) {
logSuccess('inserted into first database');
++checkCount;
});
tx1.executeSql('SELECT * from test1', [], function (tx, result) {
expect(result.rows.item(0).x).toBe(1); // selected from first database
++checkCount;
});
}, function (error) {
// ERROR NOT EXPECTED here:
logError('transaction 1 failed ' + error);
expect(error.message).toBe('--');
done.fail();
}, function () {
logSuccess('transaction 1 committed');
if (++checkCount === expectedCheckCount) done();
});
});
it(suiteName + ' same database file with separate writer/reader db handles', function (done) {
var dbname = 'writer-reader-test.db';
var dbw = openDatabase(dbname, "1.0", "Demo", DEFAULT_SIZE);
var dbr = openDatabase(dbname, "1.0", "Demo", DEFAULT_SIZE);
dbw.transaction(function (tx) {
tx.executeSql('DROP TABLE IF EXISTS tt');
tx.executeSql('CREATE TABLE IF NOT EXISTS tt (test_data)');
tx.executeSql('INSERT INTO tt VALUES (?)', ['My-test-data']);
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error.message);
expect(error.message).toBe('--');
done.fail();
}, function() {
dbr.readTransaction(function (tx) {
tx.executeSql('SELECT test_data from tt', [], function (tx, result) {
expect(result.rows.item(0).test_data).toBe('My-test-data'); // read data from reader handle
done();
});
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error.message);
expect(error.message).toBe('--');
done.fail();
});
});
});
it(suiteName + ' same database file with multiple writer db handles', function (done) {
var dbname = 'multi-writer-test.db';
var dbw1 = openDatabase(dbname, "1.0", "Demo", DEFAULT_SIZE);
var dbw2 = openDatabase(dbname, "1.0", "Demo", DEFAULT_SIZE);
var dbr = openDatabase(dbname, "1.0", "Demo", DEFAULT_SIZE);
dbw1.transaction(function (tx) {
tx.executeSql('DROP TABLE IF EXISTS tt');
tx.executeSql('CREATE TABLE IF NOT EXISTS tt (test_data)');
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error.message);
expect(error.message).toBe('--');
done.fail();
}, function() {
dbw2.transaction(function (tx) {
tx.executeSql('INSERT INTO tt VALUES (?)', ['My-test-data']);
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error.message);
expect(error.message).toBe('--');
done.fail();
}, function() {
dbr.readTransaction(function (tx) {
tx.executeSql('SELECT test_data from tt', [], function (tx, result) {
expect(result.rows.item(0).test_data).toBe('My-test-data'); // read data from reader handle
done();
});
}, function(error) {
// ERROR NOT EXPECTED here:
logError(error.message);
expect(error.message).toBe('--');
done.fail();
});
});
});
});
});
}
}
if (window.hasBrowser) mytests();
else exports.defineAutoTests = mytests;
/* vim: set expandtab : */