/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const { Sqlite } = ChromeUtils.importESModule( "resource://gre/modules/Sqlite.sys.mjs" ); const { FirefoxAdapter } = ChromeUtils.importESModule( "resource://services-common/kinto-storage-adapter.sys.mjs" ); // set up what we need to make storage adapters const kintoFilename = "kinto.sqlite"; function do_get_kinto_connection() { return FirefoxAdapter.openConnection({ path: kintoFilename }); } 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); run_next_test(); }); } function test_collection_operations() { add_task(async function test_kinto_clear() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); await adapter.clear(); await sqliteHandle.close(); }); // test creating new records... and getting them again add_task(async function test_kinto_create_new_get_existing() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); let record = { id: "test-id", foo: "bar" }; await adapter.execute(transaction => transaction.create(record)); let newRecord = await adapter.get("test-id"); // ensure the record is the same as when it was added deepEqual(record, newRecord); await sqliteHandle.close(); }); // test removing records add_task(async function test_kinto_can_remove_some_records() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); // create a second record let record = { id: "test-id-2", foo: "baz" }; await adapter.execute(transaction => transaction.create(record)); let newRecord = await adapter.get("test-id-2"); deepEqual(record, newRecord); // delete the record await adapter.execute(transaction => transaction.delete(record.id)); newRecord = await adapter.get(record.id); // ... and ensure it's no longer there Assert.equal(newRecord, undefined); // ensure the other record still exists newRecord = await adapter.get("test-id"); Assert.notEqual(newRecord, undefined); await sqliteHandle.close(); }); // test getting records that don't exist add_task(async function test_kinto_get_non_existant() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); // Kinto expects adapters to either: let newRecord = await adapter.get("missing-test-id"); // resolve with an undefined record Assert.equal(newRecord, undefined); await sqliteHandle.close(); }); // test updating records... and getting them again add_task(async function test_kinto_update_get_existing() { let sqliteHandle = await 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" }; await adapter.clear(); await adapter.execute(transaction => transaction.create(originalRecord)); await adapter.execute(transaction => transaction.update(updatedRecord)); // ensure the record exists let newRecord = await adapter.get("test-id"); // ensure the record is the same as when it was added deepEqual(updatedRecord, newRecord); await sqliteHandle.close(); }); // test listing records add_task(async function test_kinto_list() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); let originalRecord = { id: "test-id-1", foo: "bar" }; let records = await adapter.list(); Assert.equal(records.length, 1); await adapter.execute(transaction => transaction.create(originalRecord)); records = await adapter.list(); Assert.equal(records.length, 2); await sqliteHandle.close(); }); // test aborting transaction add_task(async function test_kinto_aborting_transaction() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); await adapter.clear(); let record = { id: 1, foo: "bar" }; let error = null; try { await adapter.execute(transaction => { transaction.create(record); throw new Error("unexpected"); }); } catch (e) { error = e; } Assert.notEqual(error, null); let records = await adapter.list(); Assert.equal(records.length, 0); await sqliteHandle.close(); }); // test save and get last modified add_task(async function test_kinto_last_modified() { const initialValue = 0; const intendedValue = 12345678; let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); let lastModified = await adapter.getLastModified(); Assert.equal(lastModified, initialValue); let result = await adapter.saveLastModified(intendedValue); Assert.equal(result, intendedValue); lastModified = await adapter.getLastModified(); Assert.equal(lastModified, intendedValue); // test saveLastModified parses values correctly result = await adapter.saveLastModified(" " + intendedValue + " blah"); // should resolve with the parsed int Assert.equal(result, intendedValue); // and should have saved correctly lastModified = await adapter.getLastModified(); Assert.equal(lastModified, intendedValue); await sqliteHandle.close(); }); // test loadDump(records) add_task(async function test_kinto_import_records() { let sqliteHandle = await 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 = await adapter.loadDump([record1, record2]); Assert.equal(impactedRecords.length, 2); let newRecord1 = await adapter.get("1"); // ensure the record is the same as when it was added deepEqual(record1, newRecord1); let newRecord2 = await adapter.get("2"); // ensure the record is the same as when it was added deepEqual(record2, newRecord2); await sqliteHandle.close(); }); add_task(async function test_kinto_import_records_should_override_existing() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); await adapter.clear(); let records = await adapter.list(); Assert.equal(records.length, 0); let impactedRecords = await adapter.loadDump([ { id: 1, foo: "bar" }, { id: 2, foo: "baz" }, ]); Assert.equal(impactedRecords.length, 2); await adapter.loadDump([ { id: 1, foo: "baz" }, { id: 3, foo: "bab" }, ]); records = await adapter.list(); Assert.equal(records.length, 3); let newRecord1 = await adapter.get("1"); deepEqual(newRecord1.foo, "baz"); await sqliteHandle.close(); }); add_task(async function test_import_updates_lastModified() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); await adapter.loadDump([ { id: 1, foo: "bar", last_modified: 1457896541 }, { id: 2, foo: "baz", last_modified: 1458796542 }, ]); let lastModified = await adapter.getLastModified(); Assert.equal(lastModified, 1458796542); await sqliteHandle.close(); }); add_task(async function test_import_preserves_older_lastModified() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); await adapter.saveLastModified(1458796543); await adapter.loadDump([ { id: 1, foo: "bar", last_modified: 1457896541 }, { id: 2, foo: "baz", last_modified: 1458796542 }, ]); let lastModified = await adapter.getLastModified(); Assert.equal(lastModified, 1458796543); await sqliteHandle.close(); }); add_task(async function test_save_metadata_preserves_lastModified() { let sqliteHandle = await do_get_kinto_connection(); let adapter = do_get_kinto_adapter(sqliteHandle); await adapter.saveLastModified(42); await adapter.saveMetadata({ id: "col" }); let lastModified = await adapter.getLastModified(); Assert.equal(lastModified, 42); await 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 let kintoDB = do_get_kinto_db(); Assert.ok(!kintoDB.exists()); run_next_test(); }); test_collection_operations(); cleanup_kinto(); run_next_test(); }); // this is the closest we can get to a schema version upgrade at v1 - test an // existing database add_test(function test_creation_from_empty_db() { add_test(function test_create_from_empty_db() { // place an empty kinto db file in the profile let profile = do_get_profile(); let emptyDB = do_get_file("test_storage_adapter/empty.sqlite"); emptyDB.copyTo(profile, kintoFilename); run_next_test(); }); test_collection_operations(); cleanup_kinto(); run_next_test(); }); // test schema version upgrade at v2 add_test(function test_migration_from_v1_to_v2() { add_test(function test_migrate_from_v1_to_v2() { // place an empty kinto db file in the profile let profile = do_get_profile(); let v1DB = do_get_file("test_storage_adapter/v1.sqlite"); v1DB.copyTo(profile, kintoFilename); run_next_test(); }); add_test(async function schema_is_update_from_1_to_2() { // The `v1.sqlite` has schema version 1. let sqliteHandle = await Sqlite.openConnection({ path: kintoFilename }); Assert.equal(await sqliteHandle.getSchemaVersion(), 1); await sqliteHandle.close(); // The `.openConnection()` migrates it to version 2. sqliteHandle = await FirefoxAdapter.openConnection({ path: kintoFilename }); Assert.equal(await sqliteHandle.getSchemaVersion(), 2); await sqliteHandle.close(); run_next_test(); }); test_collection_operations(); cleanup_kinto(); run_next_test(); });