/* import-globals-from ../../../common/tests/unit/head_helpers.js */ const { SyncHistory } = ChromeUtils.importESModule( "resource://services-settings/SyncHistory.sys.mjs" ); const { RemoteSettingsClient } = ChromeUtils.importESModule( "resource://services-settings/RemoteSettingsClient.sys.mjs" ); const { RemoteSettings } = ChromeUtils.importESModule( "resource://services-settings/remote-settings.sys.mjs" ); const { Utils } = ChromeUtils.importESModule( "resource://services-settings/Utils.sys.mjs" ); const PREF_SETTINGS_SERVER = "services.settings.server"; const CHANGES_PATH = "/v1" + Utils.CHANGES_PATH; const BROKEN_SYNC_THRESHOLD = 10; // See default pref value let server; let client; let maybeSyncBackup; async function clear_state() { // Disable logging output. Services.prefs.setStringPref("services.settings.loglevel", "critical"); // Pull data from the test server. Services.prefs.setStringPref( PREF_SETTINGS_SERVER, `http://localhost:${server.identity.primaryPort}/v1` ); // Clear sync history. await new SyncHistory("").clear(); // Simulate a response whose ETag gets incremented on each call // (in order to generate several history entries, indexed by timestamp). let timestamp = 1337; server.registerPathHandler(CHANGES_PATH, (request, response) => { response.setStatusLine(null, 200, "OK"); response.setHeader("Content-Type", "application/json; charset=UTF-8"); response.setHeader("Date", new Date(1000000).toUTCString()); response.setHeader("ETag", `"${timestamp}"`); response.write( JSON.stringify({ timestamp, changes: [ { last_modified: ++timestamp, bucket: "main", collection: "desktop-manager", }, ], }) ); }); // Restore original maybeSync() method between each test. client.maybeSync = maybeSyncBackup; } function run_test() { // Set up an HTTP Server server = new HttpServer(); server.start(-1); client = RemoteSettings("desktop-manager"); maybeSyncBackup = client.maybeSync; run_next_test(); registerCleanupFunction(() => { server.stop(() => {}); // Restore original maybeSync() method when test suite is done. client.maybeSync = maybeSyncBackup; }); } add_task(clear_state); add_task(async function test_db_is_destroyed_when_sync_is_broken() { // Simulate a successful sync. client.maybeSync = async () => { // Store some data in local DB. await client.db.importChanges({}, 1515, []); }; await RemoteSettings.pollChanges({ trigger: "timer" }); // Register a client with a failing sync method. client.maybeSync = () => { throw new RemoteSettingsClient.InvalidSignatureError( "main/desktop-manager" ); }; // Now obtain several failures in a row. for (var i = 0; i < BROKEN_SYNC_THRESHOLD; i++) { try { await RemoteSettings.pollChanges({ trigger: "timer" }); } catch (e) {} } // Synchronization is in broken state. Assert.equal( await client.db.getLastModified(), 1515, "Local DB was not destroyed yet" ); // Synchronize again. Broken state will be detected. try { await RemoteSettings.pollChanges({ trigger: "timer" }); } catch (e) {} // DB was destroyed. Assert.equal( await client.db.getLastModified(), null, "Local DB was destroyed" ); }); add_task(clear_state); add_task(async function test_db_is_not_destroyed_when_state_is_server_error() { // Since we don't mock the server endpoints to obtain the changeset of this // collection, the call to `maybeSync()` will fail with network errors. // Store some data in local DB. await client.db.importChanges({}, 1515, []); // Now obtain several failures in a row. let lastError; for (var i = 0; i < BROKEN_SYNC_THRESHOLD + 1; i++) { try { await RemoteSettings.pollChanges({ trigger: "timer" }); } catch (e) { lastError = e; } } Assert.ok( /Cannot parse server content/.test(lastError.message), "Error is about server" ); // DB was not destroyed. Assert.equal( await client.db.getLastModified(), 1515, "Local DB was not destroyed" ); }); add_task(clear_state);