test_uistate.js (12233B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { UIState } = ChromeUtils.importESModule( 7 "resource://services-sync/UIState.sys.mjs" 8 ); 9 10 const UIStateInternal = UIState._internal; 11 12 add_task(async function test_isReady_unconfigured() { 13 UIState.reset(); 14 15 let refreshState = sinon.spy(UIStateInternal, "refreshState"); 16 17 // On the first call, returns false 18 // Does trigger a refresh of the state - even though services.sync.username 19 // is undefined we still need to check the account state. 20 ok(!UIState.isReady()); 21 // resfreshState is called when idle - so only check after idle. 22 await new Promise(resolve => { 23 Services.tm.idleDispatchToMainThread(resolve); 24 }); 25 ok(refreshState.called); 26 refreshState.resetHistory(); 27 28 // On subsequent calls, only return true 29 ok(UIState.isReady()); 30 ok(!refreshState.called); 31 32 refreshState.restore(); 33 }); 34 35 add_task(async function test_isReady_signedin() { 36 UIState.reset(); 37 Services.prefs.setStringPref("services.sync.username", "foo"); 38 39 let refreshState = sinon.spy(UIStateInternal, "refreshState"); 40 41 // On the first call, returns false and triggers a refresh of the state 42 ok(!UIState.isReady()); 43 await new Promise(resolve => { 44 Services.tm.idleDispatchToMainThread(resolve); 45 }); 46 ok(refreshState.calledOnce); 47 refreshState.resetHistory(); 48 49 // On subsequent calls, only return true 50 ok(UIState.isReady()); 51 ok(!refreshState.called); 52 53 refreshState.restore(); 54 }); 55 56 add_task(async function test_refreshState_signedin() { 57 UIState.reset(); 58 const fxAccountsOrig = UIStateInternal.fxAccounts; 59 60 const now = new Date().toString(); 61 Services.prefs.setStringPref("services.sync.lastSync", now); 62 Services.prefs.setStringPref("services.sync.username", "foo@bar.com"); 63 UIStateInternal.syncing = false; 64 65 UIStateInternal.fxAccounts = { 66 getSignedInUser: () => 67 Promise.resolve({ 68 verified: true, 69 uid: "123", 70 email: "foo@bar.com", 71 displayName: "Foo Bar", 72 avatar: "https://foo/bar", 73 }), 74 hasLocalSession: () => Promise.resolve(true), 75 keys: { 76 canGetKeyForScope: () => Promise.resolve(true), 77 hasKeysForScope: () => Promise.resolve(true), 78 }, 79 }; 80 81 let state = await UIState.refresh(); 82 83 equal(state.status, UIState.STATUS_SIGNED_IN); 84 equal(state.uid, "123"); 85 equal(state.email, "foo@bar.com"); 86 equal(state.displayName, "Foo Bar"); 87 equal(state.avatarURL, "https://foo/bar"); 88 equal(state.lastSync, now); 89 equal(state.syncing, false); 90 equal(state.hasSyncKeys, true); 91 92 UIStateInternal.fxAccounts = fxAccountsOrig; 93 Services.prefs.clearUserPref("services.sync.username"); 94 }); 95 96 add_task(async function test_refreshState_syncButNoFxA() { 97 UIState.reset(); 98 const fxAccountsOrig = UIStateInternal.fxAccounts; 99 100 const now = new Date().toString(); 101 Services.prefs.setStringPref("services.sync.lastSync", now); 102 Services.prefs.setStringPref("services.sync.username", "test@test.com"); 103 UIStateInternal.syncing = false; 104 105 UIStateInternal.fxAccounts = { 106 getSignedInUser: () => Promise.resolve(null), 107 }; 108 109 let state = await UIState.refresh(); 110 111 equal(state.status, UIState.STATUS_LOGIN_FAILED); 112 equal(state.uid, undefined); 113 equal(state.email, "test@test.com"); 114 equal(state.displayName, undefined); 115 equal(state.avatarURL, undefined); 116 equal(state.lastSync, undefined); // only set when STATUS_SIGNED_IN. 117 equal(state.syncing, false); 118 119 UIStateInternal.fxAccounts = fxAccountsOrig; 120 Services.prefs.clearUserPref("services.sync.lastSync"); 121 Services.prefs.clearUserPref("services.sync.username"); 122 }); 123 124 add_task(async function test_refreshState_signedin_profile_unavailable() { 125 UIState.reset(); 126 const fxAccountsOrig = UIStateInternal.fxAccounts; 127 128 const now = new Date().toString(); 129 Services.prefs.setStringPref("services.sync.lastSync", now); 130 Services.prefs.setStringPref("services.sync.username", "test@test.com"); 131 UIStateInternal.syncing = false; 132 133 UIStateInternal.fxAccounts = { 134 getSignedInUser: () => 135 Promise.resolve({ verified: true, uid: "123", email: "foo@bar.com" }), 136 hasLocalSession: () => Promise.resolve(true), 137 keys: { 138 canGetKeyForScope: () => Promise.resolve(true), 139 hasKeysForScope: () => Promise.resolve(true), 140 }, 141 _internal: { 142 profile: { 143 getProfile: () => { 144 return Promise.reject(new Error("Profile unavailable")); 145 }, 146 }, 147 }, 148 }; 149 150 let state = await UIState.refresh(); 151 152 equal(state.status, UIState.STATUS_SIGNED_IN); 153 equal(state.uid, "123"); 154 equal(state.email, "foo@bar.com"); 155 equal(state.displayName, undefined); 156 equal(state.avatarURL, undefined); 157 equal(state.lastSync, now); 158 equal(state.syncing, false); 159 160 UIStateInternal.fxAccounts = fxAccountsOrig; 161 Services.prefs.clearUserPref("services.sync.lastSync"); 162 Services.prefs.clearUserPref("services.sync.username"); 163 }); 164 165 add_task(async function test_refreshState_unverified() { 166 UIState.reset(); 167 const fxAccountsOrig = UIStateInternal.fxAccounts; 168 169 UIStateInternal.fxAccounts = { 170 getSignedInUser: () => 171 Promise.resolve({ verified: false, uid: "123", email: "foo@bar.com" }), 172 hasLocalSession: () => Promise.resolve(true), 173 keys: { 174 hasKeysForScope: () => Promise.resolve(true), 175 }, 176 }; 177 178 let state = await UIState.refresh(); 179 180 equal(state.status, UIState.STATUS_NOT_VERIFIED); 181 equal(state.uid, "123"); 182 equal(state.email, "foo@bar.com"); 183 equal(state.displayName, undefined); 184 equal(state.avatarURL, undefined); 185 equal(state.lastSync, undefined); 186 187 UIStateInternal.fxAccounts = fxAccountsOrig; 188 }); 189 190 add_task(async function test_refreshState_unverified_nosession() { 191 UIState.reset(); 192 const fxAccountsOrig = UIStateInternal.fxAccounts; 193 194 UIStateInternal.fxAccounts = { 195 getSignedInUser: () => 196 Promise.resolve({ verified: false, uid: "123", email: "foo@bar.com" }), 197 hasLocalSession: () => Promise.resolve(false), 198 keys: { 199 hasKeysForScope: () => Promise.resolve(true), 200 }, 201 }; 202 203 let state = await UIState.refresh(); 204 205 // No session should "win" over the unverified state. 206 equal(state.status, UIState.STATUS_LOGIN_FAILED); 207 equal(state.uid, "123"); 208 equal(state.email, "foo@bar.com"); 209 equal(state.displayName, undefined); 210 equal(state.avatarURL, undefined); 211 equal(state.lastSync, undefined); 212 213 UIStateInternal.fxAccounts = fxAccountsOrig; 214 }); 215 216 add_task(async function test_refreshState_loginFailed() { 217 UIState.reset(); 218 const fxAccountsOrig = UIStateInternal.fxAccounts; 219 220 let loginFailed = sinon.stub(UIStateInternal, "_loginFailed"); 221 loginFailed.returns(true); 222 223 UIStateInternal.fxAccounts = { 224 getSignedInUser: () => 225 Promise.resolve({ verified: true, uid: "123", email: "foo@bar.com" }), 226 keys: { 227 canGetKeyForScope: () => Promise.resolve(true), 228 hasKeysForScope: () => Promise.resolve(true), 229 }, 230 }; 231 232 let state = await UIState.refresh(); 233 234 equal(state.status, UIState.STATUS_LOGIN_FAILED); 235 equal(state.uid, "123"); 236 equal(state.email, "foo@bar.com"); 237 equal(state.displayName, undefined); 238 equal(state.avatarURL, undefined); 239 equal(state.lastSync, undefined); 240 241 loginFailed.restore(); 242 UIStateInternal.fxAccounts = fxAccountsOrig; 243 }); 244 245 add_task(async function test_observer_refreshState() { 246 let refreshState = sinon.spy(UIStateInternal, "refreshState"); 247 248 let shouldRefresh = [ 249 "weave:service:login:got-hashed-id", 250 "weave:service:login:error", 251 "weave:service:ready", 252 "fxaccounts:onverified", 253 "fxaccounts:onlogin", 254 "fxaccounts:onlogout", 255 "fxaccounts:profilechange", 256 ]; 257 258 for (let topic of shouldRefresh) { 259 let uiUpdateObserved = observeUIUpdate(); 260 Services.obs.notifyObservers(null, topic); 261 await uiUpdateObserved; 262 ok(refreshState.calledOnce); 263 refreshState.resetHistory(); 264 } 265 266 refreshState.restore(); 267 }); 268 269 // Drive the UIState in a configured state. 270 async function configureUIState(syncing, lastSync = new Date()) { 271 UIState.reset(); 272 const fxAccountsOrig = UIStateInternal.fxAccounts; 273 274 UIStateInternal._syncing = syncing; 275 Services.prefs.setStringPref("services.sync.lastSync", lastSync.toString()); 276 Services.prefs.setStringPref("services.sync.username", "test@test.com"); 277 278 UIStateInternal.fxAccounts = { 279 getSignedInUser: () => 280 Promise.resolve({ verified: true, uid: "123", email: "foo@bar.com" }), 281 hasLocalSession: () => Promise.resolve(true), 282 keys: { 283 canGetKeyForScope: () => Promise.resolve(true), 284 hasKeysForScope: () => Promise.resolve(true), 285 }, 286 }; 287 await UIState.refresh(); 288 UIStateInternal.fxAccounts = fxAccountsOrig; 289 } 290 291 add_task(async function test_syncStarted() { 292 await configureUIState(false); 293 294 const oldState = Object.assign({}, UIState.get()); 295 ok(!oldState.syncing); 296 297 let uiUpdateObserved = observeUIUpdate(); 298 Services.obs.notifyObservers(null, "weave:service:sync:start"); 299 await uiUpdateObserved; 300 301 const newState = Object.assign({}, UIState.get()); 302 ok(newState.syncing); 303 }); 304 305 add_task(async function test_syncFinished() { 306 let yesterday = new Date(); 307 yesterday.setDate(yesterday.getDate() - 1); 308 await configureUIState(true, yesterday); 309 310 const oldState = Object.assign({}, UIState.get()); 311 ok(oldState.syncing); 312 313 let uiUpdateObserved = observeUIUpdate(); 314 Services.prefs.setStringPref("services.sync.lastSync", new Date().toString()); 315 Services.obs.notifyObservers(null, "weave:service:sync:finish"); 316 await uiUpdateObserved; 317 318 const newState = Object.assign({}, UIState.get()); 319 ok(!newState.syncing); 320 Assert.greater(new Date(newState.lastSync), new Date(oldState.lastSync)); 321 }); 322 323 add_task(async function test_syncError() { 324 let yesterday = new Date(); 325 yesterday.setDate(yesterday.getDate() - 1); 326 await configureUIState(true, yesterday); 327 328 const oldState = Object.assign({}, UIState.get()); 329 ok(oldState.syncing); 330 331 let uiUpdateObserved = observeUIUpdate(); 332 Services.obs.notifyObservers(null, "weave:service:sync:error"); 333 await uiUpdateObserved; 334 335 const newState = Object.assign({}, UIState.get()); 336 ok(!newState.syncing); 337 deepEqual(newState.lastSync, oldState.lastSync); 338 }); 339 340 add_task(async function test_refreshState_signedin_with_synckeys() { 341 UIState.reset(); 342 const fxAccountsOrig = UIStateInternal.fxAccounts; 343 344 Services.prefs.setStringPref("services.sync.username", "test@test.com"); 345 346 UIStateInternal.fxAccounts = { 347 getSignedInUser: () => 348 Promise.resolve({ 349 verified: true, 350 uid: "123", 351 email: "foo@bar.com", 352 displayName: "Foo Bar", 353 }), 354 hasLocalSession: () => Promise.resolve(true), 355 keys: { 356 canGetKeyForScope: () => Promise.resolve(true), 357 hasKeysForScope: () => Promise.resolve(true), 358 }, 359 }; 360 361 let state = await UIState.refresh(); 362 363 equal(state.status, UIState.STATUS_SIGNED_IN); 364 equal(state.syncEnabled, true); 365 equal(state.uid, "123"); 366 equal(state.email, "foo@bar.com"); 367 368 UIStateInternal.fxAccounts = fxAccountsOrig; 369 Services.prefs.clearUserPref("services.sync.username"); 370 }); 371 372 // Testing third-party auth does not enable sync (should be impossible without keys) 373 add_task(async function test_refreshState_third_party_auth_no_sync() { 374 UIState.reset(); 375 const fxAccountsOrig = UIStateInternal.fxAccounts; 376 377 // Mock signing in but NO keys (third-party auth) 378 UIStateInternal.fxAccounts = { 379 getSignedInUser: () => 380 Promise.resolve({ 381 verified: true, 382 uid: "123", 383 email: "foo@bar.com", 384 }), 385 hasLocalSession: () => Promise.resolve(true), 386 keys: { 387 hasKeysForScope: () => Promise.resolve(false), 388 }, 389 }; 390 391 let state = await UIState.refresh(); 392 393 equal(state.status, UIState.STATUS_SIGNED_IN); 394 equal(state.syncEnabled, false); 395 equal(state.hasSyncKeys, false); 396 equal(state.uid, "123"); 397 equal(state.email, "foo@bar.com"); 398 399 UIStateInternal.fxAccounts = fxAccountsOrig; 400 }); 401 402 function observeUIUpdate() { 403 return new Promise(resolve => { 404 let obs = (aSubject, aTopic) => { 405 Services.obs.removeObserver(obs, aTopic); 406 const state = UIState.get(); 407 resolve(state); 408 }; 409 Services.obs.addObserver(obs, UIState.ON_UPDATE); 410 }); 411 }