test_remote_settings_recover_broken.js (3707B)
1 const PREF_SETTINGS_SERVER = "services.settings.server"; 2 const CHANGES_PATH = "/v1" + Utils.CHANGES_PATH; 3 const BROKEN_SYNC_THRESHOLD = 10; // See default pref value 4 5 let server; 6 let client; 7 let maybeSyncBackup; 8 9 async function clear_state() { 10 // Disable logging output. 11 Services.prefs.setStringPref("services.settings.loglevel", "critical"); 12 // Pull data from the test server. 13 Services.prefs.setStringPref( 14 PREF_SETTINGS_SERVER, 15 `http://localhost:${server.identity.primaryPort}/v1` 16 ); 17 18 // Clear sync history. 19 await new SyncHistory("").clear(); 20 21 // Simulate a response whose ETag gets incremented on each call 22 // (in order to generate several history entries, indexed by timestamp). 23 let timestamp = 1337; 24 server.registerPathHandler(CHANGES_PATH, (request, response) => { 25 response.setStatusLine(null, 200, "OK"); 26 response.setHeader("Content-Type", "application/json; charset=UTF-8"); 27 response.setHeader("Date", new Date(1000000).toUTCString()); 28 response.setHeader("ETag", `"${timestamp}"`); 29 response.write( 30 JSON.stringify({ 31 timestamp, 32 changes: [ 33 { 34 last_modified: ++timestamp, 35 bucket: "main", 36 collection: "desktop-manager", 37 }, 38 ], 39 }) 40 ); 41 }); 42 43 // Restore original maybeSync() method between each test. 44 client.maybeSync = maybeSyncBackup; 45 } 46 47 function run_test() { 48 // Set up an HTTP Server 49 server = new HttpServer(); 50 server.start(-1); 51 52 client = RemoteSettings("desktop-manager"); 53 maybeSyncBackup = client.maybeSync; 54 55 run_next_test(); 56 57 registerCleanupFunction(() => { 58 server.stop(() => {}); 59 // Restore original maybeSync() method when test suite is done. 60 client.maybeSync = maybeSyncBackup; 61 }); 62 } 63 64 add_task(clear_state); 65 66 add_task(async function test_db_is_destroyed_when_sync_is_broken() { 67 // Simulate a successful sync. 68 client.maybeSync = async () => { 69 // Store some data in local DB. 70 await client.db.importChanges({}, 1515, []); 71 }; 72 await RemoteSettings.pollChanges({ trigger: "timer" }); 73 74 // Register a client with a failing sync method. 75 client.maybeSync = () => { 76 throw new RemoteSettingsClient.InvalidSignatureError( 77 "main/desktop-manager" 78 ); 79 }; 80 81 // Now obtain several failures in a row. 82 for (var i = 0; i < BROKEN_SYNC_THRESHOLD; i++) { 83 try { 84 await RemoteSettings.pollChanges({ trigger: "timer" }); 85 } catch (e) {} 86 } 87 88 // Synchronization is in broken state. 89 Assert.equal( 90 await client.db.getLastModified(), 91 1515, 92 "Local DB was not destroyed yet" 93 ); 94 95 // Synchronize again. Broken state will be detected. 96 try { 97 await RemoteSettings.pollChanges({ trigger: "timer" }); 98 } catch (e) {} 99 100 // DB was destroyed. 101 Assert.equal( 102 await client.db.getLastModified(), 103 null, 104 "Local DB was destroyed" 105 ); 106 }); 107 108 add_task(clear_state); 109 110 add_task(async function test_db_is_not_destroyed_when_state_is_server_error() { 111 // Since we don't mock the server endpoints to obtain the changeset of this 112 // collection, the call to `maybeSync()` will fail with network errors. 113 114 // Store some data in local DB. 115 await client.db.importChanges({}, 1515, []); 116 117 // Now obtain several failures in a row. 118 let lastError; 119 for (var i = 0; i < BROKEN_SYNC_THRESHOLD + 1; i++) { 120 try { 121 await RemoteSettings.pollChanges({ trigger: "timer" }); 122 } catch (e) { 123 lastError = e; 124 } 125 } 126 Assert.ok( 127 /Cannot parse server content/.test(lastError.message), 128 "Error is about server" 129 ); 130 // DB was not destroyed. 131 Assert.equal( 132 await client.db.getLastModified(), 133 1515, 134 "Local DB was not destroyed" 135 ); 136 }); 137 138 add_task(clear_state);