Bug 1319884 - Address FirefoxAdapter feedback from kinto.js#589, r?mgoodwin
Change FirefoxAdapter definitively to require an externally-managed
Sqlite connection in order to function. This connection must be
produced by calling an openConnection() static method, which does the
work of initializing the tables and schema. Passing any other
connection is wrong, but won't be detected at runtime, and might even
work depending on the previous state of the database. Future work
might define a new KintoSqliteConnection type that can only be
produced by this method, so that it's impossible to create an
uninitialized Kinto database.
This change, since it moves Sqlite connections out of the
FirefoxAdapter, also means that the path option is no longer handled
or provided with a default. This means that the previous default,
"kinto.sqlite", is now preserved in a bunch of places all over the
codebase. This is unfortunate, but a migration is outside the scope of
this patch.
MozReview-Commit-ID: BKJqPR3jOTq
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -33,16 +33,21 @@ const PREF_BLOCKLIST_ADDONS_CHECKED_SECO
const PREF_BLOCKLIST_PLUGINS_COLLECTION = "services.blocklist.plugins.collection";
const PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS = "services.blocklist.plugins.checked";
const PREF_BLOCKLIST_GFX_COLLECTION = "services.blocklist.gfx.collection";
const PREF_BLOCKLIST_GFX_CHECKED_SECONDS = "services.blocklist.gfx.checked";
const PREF_BLOCKLIST_ENFORCE_SIGNING = "services.blocklist.signing.enforced";
const INVALID_SIGNATURE = "Invalid content/signature";
+// FIXME: this was the default path in earlier versions of
+// FirefoxAdapter, so for backwards compatibility we maintain this
+// filename, even though it isn't descriptive of who is using it.
+this.KINTO_STORAGE_PATH = "kinto.sqlite";
+
this.FILENAME_ADDONS_JSON = "blocklist-addons.json";
this.FILENAME_GFX_JSON = "blocklist-gfx.json";
this.FILENAME_PLUGINS_JSON = "blocklist-plugins.json";
function mergeChanges(collection, localRecords, changes) {
const records = {};
// Local records by id.
localRecords.forEach((record) => records[record.id] = collection.cleanLocalFields(record));
@@ -72,24 +77,25 @@ function fetchRemoteCollection(collectio
.listRecords({sort: "id"});
}
/**
* Helper to instantiate a Kinto client based on preferences for remote server
* URL and bucket name. It uses the `FirefoxAdapter` which relies on SQLite to
* persist the local DB.
*/
-function kintoClient() {
+function kintoClient(connection) {
let base = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
let bucket = Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET);
let config = {
remote: base,
bucket: bucket,
adapter: FirefoxAdapter,
+ adapterOptions: {sqliteHandle: connection},
};
return new Kinto(config);
}
class BlocklistClient {
@@ -140,34 +146,35 @@ class BlocklistClient {
* Synchronize from Kinto server, if necessary.
*
* @param {int} lastModified the lastModified date (on the server) for
the remote collection.
* @param {Date} serverTime the current date return by the server.
* @return {Promise} which rejects on sync or process failure.
*/
maybeSync(lastModified, serverTime) {
- let db = kintoClient();
let opts = {};
let enforceCollectionSigning =
Services.prefs.getBoolPref(PREF_BLOCKLIST_ENFORCE_SIGNING);
// if there is a signerName and collection signing is enforced, add a
// hook for incoming changes that validates the signature
if (this.signerName && enforceCollectionSigning) {
opts.hooks = {
"incoming-changes": [this.validateCollectionSignature.bind(this)]
}
}
- let collection = db.collection(this.collectionName, opts);
return Task.spawn((function* syncCollection() {
+ let connection;
try {
- yield collection.db.open();
+ connection = yield FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
+ let db = kintoClient(connection);
+ let collection = db.collection(this.collectionName, opts);
let collectionLastModified = yield collection.db.getLastModified();
// If the data is up to date, there's no need to sync. We still need
// to record the fact that a check happened.
if (lastModified <= collectionLastModified) {
this.updateLastCheck(serverTime);
return;
}
@@ -200,17 +207,17 @@ class BlocklistClient {
// Read local collection of records.
let list = yield collection.list();
yield this.processCallback(list.data);
// Track last update.
this.updateLastCheck(serverTime);
} finally {
- collection.db.close();
+ yield connection.close();
}
}).bind(this));
}
/**
* Save last time server was checked in users prefs.
*
* @param {Date} serverTime the current date return by server.
--- a/services/common/kinto-storage-adapter.js
+++ b/services/common/kinto-storage-adapter.js
@@ -184,30 +184,34 @@ const createStatements = [
const currentSchemaVersion = 1;
/**
* Firefox adapter.
*
* Uses Sqlite as a backing store.
*
* Options:
- * - path: the filename/path for the Sqlite database. If absent, use SQLITE_PATH.
+ * - sqliteHandle: a handle to the Sqlite database this adapter will
+ * use as its backing store. To open such a handle, use the
+ * static openConnection() method.
*/
class FirefoxAdapter extends Kinto.adapters.BaseAdapter {
constructor(collection, options = {}) {
super();
const { sqliteHandle = null } = options;
this.collection = collection;
this._connection = sqliteHandle;
this._options = options;
}
- // We need to be capable of calling this from "outside" the adapter
- // so that someone can initialize a connection and pass it to us in
- // adapterOptions.
+ /**
+ * Initialize a Sqlite connection to be suitable for use with Kinto.
+ *
+ * This will be called automatically by open().
+ */
static _init(connection) {
return Task.spawn(function* () {
yield connection.executeTransaction(function* doSetup() {
const schema = yield connection.getSchemaVersion();
if (schema == 0) {
for (let statementName of createStatements) {
@@ -219,52 +223,40 @@ class FirefoxAdapter extends Kinto.adapt
throw new Error("Unknown database schema: " + schema);
}
});
return connection;
});
}
_executeStatement(statement, params) {
- if (!this._connection) {
- throw new Error("The storage adapter is not open");
- }
return this._connection.executeCached(statement, params);
}
- open() {
- const self = this;
- return Task.spawn(function* () {
- if (!self._connection) {
- const path = self._options.path || SQLITE_PATH;
- const opts = { path, sharedMemoryCache: false };
- self._connection = yield Sqlite.openConnection(opts).then(FirefoxAdapter._init);
- }
- });
- }
-
- close() {
- if (this._connection) {
- const promise = this._connection.close();
- this._connection = null;
- return promise;
- }
- return Promise.resolve();
+ /**
+ * Open and initialize a Sqlite connection to a database that Kinto
+ * can use. When you are done with this connection, close it by
+ * calling close().
+ *
+ * Options:
+ * - path: The path for the Sqlite database
+ *
+ * @returns SqliteConnection
+ */
+ static async openConnection(options) {
+ const opts = Object.assign({}, { sharedMemoryCache: false }, options);
+ return await Sqlite.openConnection(opts).then(this._init);
}
clear() {
const params = { collection_name: this.collection };
return this._executeStatement(statements.clearData, params);
}
execute(callback, options = { preload: [] }) {
- if (!this._connection) {
- throw new Error("The storage adapter is not open");
- }
-
let result;
const conn = this._connection;
const collection = this.collection;
return conn.executeTransaction(function* doExecuteTransaction() {
// Preload specified records from DB, within transaction.
const parameters = [
collection,
--- a/services/common/tests/unit/test_blocklist_certificates.js
+++ b/services/common/tests/unit/test_blocklist_certificates.js
@@ -9,31 +9,27 @@ const { FirefoxAdapter } = Cu.import("re
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
let server;
// set up what we need to make storage adapters
const kintoFilename = "kinto.sqlite";
-let kintoClient;
-
-function do_get_kinto_collection(collectionName) {
- if (!kintoClient) {
- let config = {
- // Set the remote to be some server that will cause test failure when
- // hit since we should never hit the server directly, only via maybeSync()
- remote: "https://firefox.settings.services.mozilla.com/v1/",
- // Set up the adapter and bucket as normal
- adapter: FirefoxAdapter,
- bucket: "blocklists"
- };
- kintoClient = new Kinto(config);
- }
- return kintoClient.collection(collectionName);
+function do_get_kinto_collection(collectionName, sqliteHandle) {
+ let config = {
+ // Set the remote to be some server that will cause test failure when
+ // hit since we should never hit the server directly, only via maybeSync()
+ remote: "https://firefox.settings.services.mozilla.com/v1/",
+ // Set up the adapter and bucket as normal
+ adapter: FirefoxAdapter,
+ adapterOptions: {sqliteHandle},
+ bucket: "blocklists"
+ };
+ return new Kinto(config).collection(collectionName);
}
// Some simple tests to demonstrate that the logic inside maybeSync works
// correctly and that simple kinto operations are working as expected. There
// are more tests for core Kinto.js (and its storage adapter) in the
// xpcshell tests under /services/common
add_task(function* test_something(){
const configPath = "/v1/";
@@ -67,32 +63,32 @@ add_task(function* test_something(){
server.registerPathHandler(configPath, handleResponse);
server.registerPathHandler(recordsPath, handleResponse);
// Test an empty db populates
let result = yield OneCRLBlocklistClient.maybeSync(2000, Date.now());
// Open the collection, verify it's been populated:
// Our test data has a single record; it should be in the local collection
- let collection = do_get_kinto_collection("certificates");
- yield collection.db.open();
+ let sqliteHandle = yield FirefoxAdapter.openConnection({path: kintoFilename});
+ let collection = do_get_kinto_collection("certificates", sqliteHandle);
let list = yield collection.list();
do_check_eq(list.data.length, 1);
- yield collection.db.close();
+ yield sqliteHandle.close();
// Test the db is updated when we call again with a later lastModified value
result = yield OneCRLBlocklistClient.maybeSync(4000, Date.now());
// Open the collection, verify it's been updated:
// Our test data now has two records; both should be in the local collection
- collection = do_get_kinto_collection("certificates");
- yield collection.db.open();
+ sqliteHandle = yield FirefoxAdapter.openConnection({path: kintoFilename});
+ collection = do_get_kinto_collection("certificates", sqliteHandle);
list = yield collection.list();
do_check_eq(list.data.length, 3);
- yield collection.db.close();
+ yield sqliteHandle.close();
// Try to maybeSync with the current lastModified value - no connection
// should be attempted.
// Clear the kinto base pref so any connections will cause a test failure
Services.prefs.clearUserPref("services.settings.server");
yield OneCRLBlocklistClient.maybeSync(4000, Date.now());
// Try again with a lastModified value at some point in the past
--- a/services/common/tests/unit/test_blocklist_clients.js
+++ b/services/common/tests/unit/test_blocklist_clients.js
@@ -9,59 +9,58 @@ const { FileUtils } = Cu.import("resourc
const { OS } = Cu.import("resource://gre/modules/osfile.jsm");
const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js");
const { FirefoxAdapter } = Cu.import("resource://services-common/kinto-storage-adapter.js");
const BlocklistClients = Cu.import("resource://services-common/blocklist-clients.js");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
+const kintoFilename = "kinto.sqlite";
const gBlocklistClients = [
{client: BlocklistClients.AddonBlocklistClient, filename: BlocklistClients.FILENAME_ADDONS_JSON, testData: ["i808","i720", "i539"]},
{client: BlocklistClients.PluginBlocklistClient, filename: BlocklistClients.FILENAME_PLUGINS_JSON, testData: ["p1044","p32","p28"]},
{client: BlocklistClients.GfxBlocklistClient, filename: BlocklistClients.FILENAME_GFX_JSON, testData: ["g204","g200","g36"]},
];
let server;
-let kintoClient;
-function kintoCollection(collectionName) {
- if (!kintoClient) {
- const config = {
- // Set the remote to be some server that will cause test failure when
- // hit since we should never hit the server directly, only via maybeSync()
- remote: "https://firefox.settings.services.mozilla.com/v1/",
- adapter: FirefoxAdapter,
- bucket: "blocklists"
- };
- kintoClient = new Kinto(config);
- }
- return kintoClient.collection(collectionName);
+function kintoCollection(collectionName, sqliteHandle) {
+ const config = {
+ // Set the remote to be some server that will cause test failure when
+ // hit since we should never hit the server directly, only via maybeSync()
+ remote: "https://firefox.settings.services.mozilla.com/v1/",
+ adapter: FirefoxAdapter,
+ adapterOptions: {sqliteHandle},
+ bucket: "blocklists"
+ };
+ return new Kinto(config).collection(collectionName);
}
function* readJSON(filepath) {
const binaryData = yield OS.File.read(filepath);
const textData = (new TextDecoder()).decode(binaryData);
return Promise.resolve(JSON.parse(textData));
}
function* clear_state() {
for (let {client} of gBlocklistClients) {
// Remove last server times.
Services.prefs.clearUserPref(client.lastCheckTimePref);
// Clear local DB.
- const collection = kintoCollection(client.collectionName);
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield FirefoxAdapter.openConnection({path: kintoFilename});
+ const collection = kintoCollection(client.collectionName, sqliteHandle);
yield collection.clear();
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
}
// Remove profile data.
for (let {filename} of gBlocklistClients) {
const blocklist = FileUtils.getFile(KEY_PROFILEDIR, [filename]);
if (blocklist.exists()) {
blocklist.remove(true);
@@ -121,21 +120,21 @@ function run_test() {
add_task(function* test_records_obtained_from_server_are_stored_in_db(){
for (let {client} of gBlocklistClients) {
// Test an empty db populates
let result = yield client.maybeSync(2000, Date.now());
// Open the collection, verify it's been populated:
// Our test data has a single record; it should be in the local collection
- let collection = kintoCollection(client.collectionName);
- yield collection.db.open();
+ const sqliteHandle = yield FirefoxAdapter.openConnection({path: kintoFilename});
+ let collection = kintoCollection(client.collectionName, sqliteHandle);
let list = yield collection.list();
equal(list.data.length, 1);
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_state);
add_task(function* test_list_is_written_to_file_in_profile(){
for (let {client, filename, testData} of gBlocklistClients) {
const profFile = FileUtils.getFile(KEY_PROFILEDIR, [filename]);
strictEqual(profFile.exists(), false);
--- a/services/common/tests/unit/test_blocklist_signatures.js
+++ b/services/common/tests/unit/test_blocklist_signatures.js
@@ -11,16 +11,17 @@ const { OneCRLBlocklistClient } = Cu.imp
let server;
const PREF_BLOCKLIST_BUCKET = "services.blocklist.bucket";
const PREF_BLOCKLIST_ENFORCE_SIGNING = "services.blocklist.signing.enforced";
const PREF_BLOCKLIST_ONECRL_COLLECTION = "services.blocklist.onecrl.collection";
const PREF_SETTINGS_SERVER = "services.settings.server";
const PREF_SIGNATURE_ROOT = "security.content.signature.root_hash";
+const kintoFilename = "kinto.sqlite";
const CERT_DIR = "test_blocklist_signatures/";
const CHAIN_FILES =
["collection_signing_ee.pem",
"collection_signing_int.pem",
"collection_signing_root.pem"];
function getFileData(file) {
@@ -56,33 +57,33 @@ function getCertChain() {
function* checkRecordCount(count) {
// open the collection manually
const base = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
const bucket = Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET);
const collectionName =
Services.prefs.getCharPref(PREF_BLOCKLIST_ONECRL_COLLECTION);
+ const sqliteHandle = yield FirefoxAdapter.openConnection({path: kintoFilename});
const config = {
remote: base,
bucket: bucket,
adapter: FirefoxAdapter,
+ adapterOptions: {sqliteHandle},
};
const db = new Kinto(config);
const collection = db.collection(collectionName);
- yield collection.db.open();
-
// Check we have the expected number of records
let records = yield collection.list();
do_check_eq(count, records.data.length);
// Close the collection so the test can exit cleanly
- yield collection.db.close();
+ yield sqliteHandle.close();
}
// Check to ensure maybeSync is called with correct values when a changes
// document contains information on when a collection was last modified
add_task(function* test_check_signatures(){
const port = server.identity.primaryPort;
// a response to give the client when the cert chain is expected
--- a/services/common/tests/unit/test_kinto.js
+++ b/services/common/tests/unit/test_kinto.js
@@ -8,45 +8,47 @@ Cu.import("resource://testing-common/htt
const BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
var server;
// set up what we need to make storage adapters
const kintoFilename = "kinto.sqlite";
-let kintoClient;
+function do_get_kinto_sqliteHandle() {
+ return FirefoxAdapter.openConnection({path: kintoFilename});
+}
-function do_get_kinto_collection() {
- if (!kintoClient) {
- let config = {
- remote:`http://localhost:${server.identity.primaryPort}/v1/`,
- headers: {Authorization: "Basic " + btoa("user:pass")},
- adapter: FirefoxAdapter
- };
- kintoClient = new Kinto(config);
- }
- return kintoClient.collection("test_collection");
+function do_get_kinto_collection(sqliteHandle, collection="test_collection") {
+ let config = {
+ remote:`http://localhost:${server.identity.primaryPort}/v1/`,
+ headers: {Authorization: "Basic " + btoa("user:pass")},
+ adapter: FirefoxAdapter,
+ adapterOptions: {sqliteHandle},
+ };
+ return new Kinto(config).collection(collection);
}
function* clear_collection() {
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
yield collection.clear();
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
}
// test some operations on a local collection
add_task(function* test_kinto_add_get() {
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
let newRecord = { foo: "bar" };
// check a record is created
let createResult = yield collection.create(newRecord);
do_check_eq(createResult.data.foo, newRecord.foo);
// check getting the record gets the same info
let getResult = yield collection.get(createResult.data.id);
deepEqual(createResult.data, getResult.data);
@@ -59,108 +61,109 @@ add_task(function* test_kinto_add_get()
// try a few creates without waiting for the first few to resolve
let promises = [];
promises.push(collection.create(newRecord));
promises.push(collection.create(newRecord));
promises.push(collection.create(newRecord));
yield collection.create(newRecord);
yield Promise.all(promises);
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
// test some operations on multiple connections
add_task(function* test_kinto_add_get() {
- const collection1 = do_get_kinto_collection();
- const collection2 = kintoClient.collection("test_collection_2");
-
+ let sqliteHandle;
try {
- yield collection1.db.open();
- yield collection2.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection1 = do_get_kinto_collection(sqliteHandle);
+ const collection2 = do_get_kinto_collection(sqliteHandle, "test_collection_2");
let newRecord = { foo: "bar" };
// perform several write operations alternately without waiting for promises
// to resolve
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(collection1.create(newRecord));
promises.push(collection2.create(newRecord));
}
// ensure subsequent operations still work
yield Promise.all([collection1.create(newRecord),
collection2.create(newRecord)]);
yield Promise.all(promises);
} finally {
- yield collection1.db.close();
- yield collection2.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
add_task(function* test_kinto_update() {
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
const newRecord = { foo: "bar" };
// check a record is created
let createResult = yield collection.create(newRecord);
do_check_eq(createResult.data.foo, newRecord.foo);
do_check_eq(createResult.data._status, "created");
// check we can update this OK
let copiedRecord = Object.assign(createResult.data, {});
deepEqual(createResult.data, copiedRecord);
copiedRecord.foo = "wibble";
let updateResult = yield collection.update(copiedRecord);
// check the field was updated
do_check_eq(updateResult.data.foo, copiedRecord.foo);
// check the status is still "created", since we haven't synced
// the record
do_check_eq(updateResult.data._status, "created");
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
add_task(function* test_kinto_clear() {
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
// create an expected number of records
const expected = 10;
const newRecord = { foo: "bar" };
for (let i = 0; i < expected; i++) {
yield collection.create(newRecord);
}
// check the collection contains the correct number
let list = yield collection.list();
do_check_eq(list.data.length, expected);
// clear the collection and check again - should be 0
yield collection.clear();
list = yield collection.list();
do_check_eq(list.data.length, 0);
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
add_task(function* test_kinto_delete(){
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
const newRecord = { foo: "bar" };
// check a record is created
let createResult = yield collection.create(newRecord);
do_check_eq(createResult.data.foo, newRecord.foo);
// check getting the record gets the same info
let getResult = yield collection.get(createResult.data.id);
deepEqual(createResult.data, getResult.data);
// delete that record
@@ -168,24 +171,25 @@ add_task(function* test_kinto_delete(){
// check the ID is set on the result
do_check_eq(getResult.data.id, deleteResult.data.id);
// and check that get no longer returns the record
try {
getResult = yield collection.get(createResult.data.id);
do_throw("there should not be a result");
} catch (e) { }
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(function* test_kinto_list(){
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
const expected = 10;
const created = [];
for (let i = 0; i < expected; i++) {
let newRecord = { foo: "test " + i };
let createResult = yield collection.create(newRecord);
created.push(createResult.data);
}
// check the collection contains the correct number
@@ -199,80 +203,84 @@ add_task(function* test_kinto_list(){
if (createdRecord.id == retrievedRecord.id) {
deepEqual(createdRecord, retrievedRecord);
found = true;
}
}
do_check_true(found);
}
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
add_task(function* test_loadDump_ignores_already_imported_records(){
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
const record = {id: "41b71c13-17e9-4ee3-9268-6a41abf9730f", title: "foo", last_modified: 1457896541};
yield collection.loadDump([record]);
let impactedRecords = yield collection.loadDump([record]);
do_check_eq(impactedRecords.length, 0);
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
add_task(function* test_loadDump_should_overwrite_old_records(){
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
const record = {id: "41b71c13-17e9-4ee3-9268-6a41abf9730f", title: "foo", last_modified: 1457896541};
yield collection.loadDump([record]);
const updated = Object.assign({}, record, {last_modified: 1457896543});
let impactedRecords = yield collection.loadDump([updated]);
do_check_eq(impactedRecords.length, 1);
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
add_task(function* test_loadDump_should_not_overwrite_unsynced_records(){
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f";
yield collection.create({id: recordId, title: "foo"}, {useRecordId: true});
const record = {id: recordId, title: "bar", last_modified: 1457896541};
let impactedRecords = yield collection.loadDump([record]);
do_check_eq(impactedRecords.length, 0);
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
add_task(function* test_loadDump_should_not_overwrite_records_without_last_modified(){
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
- yield collection.db.open();
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f";
yield collection.create({id: recordId, title: "foo"}, {synced: true});
const record = {id: recordId, title: "bar", last_modified: 1457896541};
let impactedRecords = yield collection.loadDump([record]);
do_check_eq(impactedRecords.length, 0);
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
add_task(clear_collection);
// Now do some sanity checks against a server - we're not looking to test
// core kinto.js functionality here (there is excellent test coverage in
// kinto.js), more making sure things are basically working as expected.
@@ -300,21 +308,22 @@ add_task(function* test_kinto_sync(){
} catch (e) {
dump(`${e}\n`);
}
}
server.registerPathHandler(configPath, handleResponse);
server.registerPathHandler(recordsPath, handleResponse);
// create an empty collection, sync to populate
- const collection = do_get_kinto_collection();
+ let sqliteHandle;
try {
let result;
+ sqliteHandle = yield do_get_kinto_sqliteHandle();
+ const collection = do_get_kinto_collection(sqliteHandle);
- yield collection.db.open();
result = yield collection.sync();
do_check_true(result.ok);
// our test data has a single record; it should be in the local collection
let list = yield collection.list();
do_check_eq(list.data.length, 1);
// now sync again; we should now have 2 records
@@ -326,17 +335,17 @@ add_task(function* test_kinto_sync(){
// sync again; the second records should have been modified
const before = list.data[0].title;
result = yield collection.sync();
do_check_true(result.ok);
list = yield collection.list();
const after = list.data[0].title;
do_check_neq(before, after);
} finally {
- yield collection.db.close();
+ yield sqliteHandle.close();
}
});
function run_test() {
// Set up an HTTP Server
server = new HttpServer();
server.start(-1);
--- a/services/common/tests/unit/test_storage_adapter.js
+++ b/services/common/tests/unit/test_storage_adapter.js
@@ -2,233 +2,232 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-common/kinto-offline-client.js");
Cu.import("resource://services-common/kinto-storage-adapter.js");
// set up what we need to make storage adapters
const kintoFilename = "kinto.sqlite";
-let gFirefoxAdapter = null;
+function do_get_kinto_connection() {
+ return FirefoxAdapter.openConnection({path: kintoFilename});
+}
-function do_get_kinto_adapter() {
- if (gFirefoxAdapter == null) {
- gFirefoxAdapter = new FirefoxAdapter("test");
- }
- return gFirefoxAdapter;
+function do_get_kinto_adapter(sqliteHandle) {
+ return new FirefoxAdapter("test", {sqliteHandle});
}
function do_get_kinto_db() {
let profile = do_get_profile();
let kintoDB = profile.clone();
kintoDB.append(kintoFilename);
return kintoDB;
}
function cleanup_kinto() {
add_test(function cleanup_kinto_files(){
let kintoDB = do_get_kinto_db();
// clean up the db
kintoDB.remove(false);
- // force re-creation of the adapter
- gFirefoxAdapter = null;
run_next_test();
});
}
function test_collection_operations() {
add_task(function* test_kinto_clear() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
yield adapter.clear();
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test creating new records... and getting them again
add_task(function* test_kinto_create_new_get_existing() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
let record = {id:"test-id", foo:"bar"};
yield adapter.execute((transaction) => transaction.create(record));
let newRecord = yield adapter.get("test-id");
// ensure the record is the same as when it was added
deepEqual(record, newRecord);
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test removing records
add_task(function* test_kinto_can_remove_some_records() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
// create a second record
let record = {id:"test-id-2", foo:"baz"};
yield adapter.execute((transaction) => transaction.create(record));
let newRecord = yield adapter.get("test-id-2");
deepEqual(record, newRecord);
// delete the record
yield adapter.execute((transaction) => transaction.delete(record.id));
newRecord = yield adapter.get(record.id);
// ... and ensure it's no longer there
do_check_eq(newRecord, undefined);
// ensure the other record still exists
newRecord = yield adapter.get("test-id");
do_check_neq(newRecord, undefined);
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test getting records that don't exist
add_task(function* test_kinto_get_non_existant() {
- let adapter = do_get_kinto_adapter();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
yield adapter.open();
// Kinto expects adapters to either:
let newRecord = yield adapter.get("missing-test-id");
// resolve with an undefined record
do_check_eq(newRecord, undefined);
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test updating records... and getting them again
add_task(function* test_kinto_update_get_existing() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
let originalRecord = {id:"test-id", foo:"bar"};
let updatedRecord = {id:"test-id", foo:"baz"};
yield adapter.clear();
yield adapter.execute((transaction) => transaction.create(originalRecord));
yield adapter.execute((transaction) => transaction.update(updatedRecord));
// ensure the record exists
let newRecord = yield adapter.get("test-id");
// ensure the record is the same as when it was added
deepEqual(updatedRecord, newRecord);
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test listing records
add_task(function* test_kinto_list() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
let originalRecord = {id:"test-id-1", foo:"bar"};
let records = yield adapter.list();
do_check_eq(records.length, 1);
yield adapter.execute((transaction) => transaction.create(originalRecord));
records = yield adapter.list();
do_check_eq(records.length, 2);
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test aborting transaction
add_task(function* test_kinto_aborting_transaction() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
yield adapter.clear();
let record = {id: 1, foo: "bar"};
let error = null;
try {
yield adapter.execute((transaction) => {
transaction.create(record);
throw new Error("unexpected");
});
} catch (e) {
error = e;
}
do_check_neq(error, null);
records = yield adapter.list();
do_check_eq(records.length, 0);
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test save and get last modified
add_task(function* test_kinto_last_modified() {
const initialValue = 0;
const intendedValue = 12345678;
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
let lastModified = yield adapter.getLastModified();
do_check_eq(lastModified, initialValue);
let result = yield adapter.saveLastModified(intendedValue);
do_check_eq(result, intendedValue);
lastModified = yield adapter.getLastModified();
do_check_eq(lastModified, intendedValue);
// test saveLastModified parses values correctly
result = yield adapter.saveLastModified(" " + intendedValue + " blah");
// should resolve with the parsed int
do_check_eq(result, intendedValue);
// and should have saved correctly
lastModified = yield adapter.getLastModified();
do_check_eq(lastModified, intendedValue);
- yield adapter.close();
+ yield sqliteHandle.close();
});
// test loadDump(records)
add_task(function* test_kinto_import_records() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
let record1 = {id: 1, foo: "bar"};
let record2 = {id: 2, foo: "baz"};
let impactedRecords = yield adapter.loadDump([
record1, record2
]);
do_check_eq(impactedRecords.length, 2);
let newRecord1 = yield adapter.get("1");
// ensure the record is the same as when it was added
deepEqual(record1, newRecord1);
let newRecord2 = yield adapter.get("2");
// ensure the record is the same as when it was added
deepEqual(record2, newRecord2);
- yield adapter.close();
+ yield sqliteHandle.close();
});
add_task(function* test_kinto_import_records_should_override_existing() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
yield adapter.clear();
records = yield adapter.list();
do_check_eq(records.length, 0);
let impactedRecords = yield adapter.loadDump([
{id: 1, foo: "bar"},
{id: 2, foo: "baz"},
]);
do_check_eq(impactedRecords.length, 2);
yield adapter.loadDump([
{id: 1, foo: "baz"},
{id: 3, foo: "bab"},
]);
records = yield adapter.list();
do_check_eq(records.length, 3);
let newRecord1 = yield adapter.get("1");
deepEqual(newRecord1.foo, "baz");
- yield adapter.close();
+ yield sqliteHandle.close();
});
add_task(function* test_import_updates_lastModified() {
- let adapter = do_get_kinto_adapter();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
yield adapter.open();
yield adapter.loadDump([
{id: 1, foo: "bar", last_modified: 1457896541},
{id: 2, foo: "baz", last_modified: 1458796542},
]);
let lastModified = yield adapter.getLastModified();
do_check_eq(lastModified, 1458796542);
- yield adapter.close();
+ yield sqliteHandle.close();
});
add_task(function* test_import_preserves_older_lastModified() {
- let adapter = do_get_kinto_adapter();
- yield adapter.open();
+ let sqliteHandle = yield do_get_kinto_connection();
+ let adapter = do_get_kinto_adapter(sqliteHandle);
yield adapter.saveLastModified(1458796543);
yield adapter.loadDump([
{id: 1, foo: "bar", last_modified: 1457896541},
{id: 2, foo: "baz", last_modified: 1458796542},
]);
let lastModified = yield adapter.getLastModified();
do_check_eq(lastModified, 1458796543);
- yield adapter.close();
+ yield sqliteHandle.close();
});
}
// test kinto db setup and operations in various scenarios
// test from scratch - no current existing database
add_test(function test_db_creation() {
add_test(function test_create_from_scratch() {
// ensure the file does not exist in the profile