test_telemetry.js (45204B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 const { Service } = ChromeUtils.importESModule( 5 "resource://services-sync/service.sys.mjs" 6 ); 7 const { WBORecord } = ChromeUtils.importESModule( 8 "resource://services-sync/record.sys.mjs" 9 ); 10 const { Resource } = ChromeUtils.importESModule( 11 "resource://services-sync/resource.sys.mjs" 12 ); 13 const { RotaryEngine } = ChromeUtils.importESModule( 14 "resource://testing-common/services/sync/rotaryengine.sys.mjs" 15 ); 16 const { getFxAccountsSingleton } = ChromeUtils.importESModule( 17 "resource://gre/modules/FxAccounts.sys.mjs" 18 ); 19 const fxAccounts = getFxAccountsSingleton(); 20 21 function SteamStore(engine) { 22 Store.call(this, "Steam", engine); 23 } 24 Object.setPrototypeOf(SteamStore.prototype, Store.prototype); 25 26 function SteamTracker(name, engine) { 27 LegacyTracker.call(this, name || "Steam", engine); 28 } 29 Object.setPrototypeOf(SteamTracker.prototype, LegacyTracker.prototype); 30 31 function SteamEngine(service) { 32 SyncEngine.call(this, "steam", service); 33 } 34 35 SteamEngine.prototype = { 36 _storeObj: SteamStore, 37 _trackerObj: SteamTracker, 38 _errToThrow: null, 39 problemsToReport: null, 40 async _sync() { 41 if (this._errToThrow) { 42 throw this._errToThrow; 43 } 44 }, 45 getValidator() { 46 return new SteamValidator(); 47 }, 48 }; 49 Object.setPrototypeOf(SteamEngine.prototype, SyncEngine.prototype); 50 51 function BogusEngine(service) { 52 SyncEngine.call(this, "bogus", service); 53 } 54 55 BogusEngine.prototype = Object.create(SteamEngine.prototype); 56 57 class SteamValidator { 58 async canValidate() { 59 return true; 60 } 61 62 async validate(engine) { 63 return { 64 problems: new SteamValidationProblemData(engine.problemsToReport), 65 version: 1, 66 duration: 0, 67 recordCount: 0, 68 }; 69 } 70 } 71 72 class SteamValidationProblemData { 73 constructor(problemsToReport = []) { 74 this.problemsToReport = problemsToReport; 75 } 76 77 getSummary() { 78 return this.problemsToReport; 79 } 80 } 81 82 async function cleanAndGo(engine, server) { 83 await engine._tracker.clearChangedIDs(); 84 for (const pref of Svc.PrefBranch.getChildList("")) { 85 Svc.PrefBranch.clearUserPref(pref); 86 } 87 syncTestLogging(); 88 Service.recordManager.clearCache(); 89 await promiseStopServer(server); 90 } 91 92 add_task(async function setup() { 93 // Avoid addon manager complaining about not being initialized 94 await Service.engineManager.unregister("addons"); 95 await Service.engineManager.unregister("extension-storage"); 96 }); 97 98 add_task(async function test_basic() { 99 enableValidationPrefs(); 100 101 let helper = track_collections_helper(); 102 let upd = helper.with_updated_collection; 103 104 let handlers = { 105 "/1.1/johndoe/info/collections": helper.handler, 106 "/1.1/johndoe/storage/crypto/keys": upd( 107 "crypto", 108 new ServerWBO("keys").handler() 109 ), 110 "/1.1/johndoe/storage/meta/global": upd( 111 "meta", 112 new ServerWBO("global").handler() 113 ), 114 }; 115 116 let collections = [ 117 "clients", 118 "bookmarks", 119 "forms", 120 "history", 121 "passwords", 122 "prefs", 123 "tabs", 124 ]; 125 126 for (let coll of collections) { 127 handlers["/1.1/johndoe/storage/" + coll] = upd( 128 coll, 129 new ServerCollection({}, true).handler() 130 ); 131 } 132 133 let server = httpd_setup(handlers); 134 await configureIdentity({ username: "johndoe" }, server); 135 136 let ping = await wait_for_ping(() => Service.sync(), true, true); 137 138 // Check the "os" block - we can't really check specific values, but can 139 // check it smells sane. 140 ok(ping.os, "there is an OS block"); 141 ok("name" in ping.os, "there is an OS name"); 142 ok("version" in ping.os, "there is an OS version"); 143 ok("locale" in ping.os, "there is an OS locale"); 144 145 for (const pref of Svc.PrefBranch.getChildList("")) { 146 Svc.PrefBranch.clearUserPref(pref); 147 } 148 await promiseStopServer(server); 149 }); 150 151 add_task(async function test_processIncoming_error() { 152 let engine = Service.engineManager.get("bookmarks"); 153 await engine.initialize(); 154 let store = engine._store; 155 let server = await serverForFoo(engine); 156 await SyncTestingInfrastructure(server); 157 let collection = server.user("foo").collection("bookmarks"); 158 try { 159 // Create a bogus record that when synced down will provoke a 160 // network error which in turn provokes an exception in _processIncoming. 161 const BOGUS_GUID = "zzzzzzzzzzzz"; 162 let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!"); 163 bogus_record.get = function get() { 164 throw new Error("Sync this!"); 165 }; 166 // Make the 10 minutes old so it will only be synced in the toFetch phase. 167 bogus_record.modified = Date.now() / 1000 - 60 * 10; 168 await engine.setLastSync(Date.now() / 1000 - 60); 169 engine.toFetch = new SerializableSet([BOGUS_GUID]); 170 171 let error, pingPayload, fullPing; 172 try { 173 await sync_engine_and_validate_telem( 174 engine, 175 true, 176 (errPing, fullErrPing) => { 177 pingPayload = errPing; 178 fullPing = fullErrPing; 179 } 180 ); 181 } catch (ex) { 182 error = ex; 183 } 184 ok(!!error); 185 ok(!!pingPayload); 186 187 equal(fullPing.uid, "f".repeat(32)); // as setup by SyncTestingInfrastructure 188 deepEqual(pingPayload.failureReason, { 189 name: "httperror", 190 code: 500, 191 }); 192 193 equal(pingPayload.engines.length, 1); 194 195 equal(pingPayload.engines[0].name, "bookmarks-buffered"); 196 deepEqual(pingPayload.engines[0].failureReason, { 197 name: "httperror", 198 code: 500, 199 }); 200 } finally { 201 await store.wipe(); 202 await cleanAndGo(engine, server); 203 } 204 }); 205 206 add_task(async function test_uploading() { 207 let engine = Service.engineManager.get("bookmarks"); 208 await engine.initialize(); 209 let store = engine._store; 210 let server = await serverForFoo(engine); 211 await SyncTestingInfrastructure(server); 212 213 let bmk = await PlacesUtils.bookmarks.insert({ 214 parentGuid: PlacesUtils.bookmarks.toolbarGuid, 215 url: "http://getfirefox.com/", 216 title: "Get Firefox!", 217 }); 218 219 try { 220 let ping = await sync_engine_and_validate_telem(engine, false); 221 ok(!!ping); 222 equal(ping.engines.length, 1); 223 equal(ping.engines[0].name, "bookmarks-buffered"); 224 ok(!!ping.engines[0].outgoing); 225 greater(ping.engines[0].outgoing[0].sent, 0); 226 ok(!ping.engines[0].incoming); 227 228 await PlacesUtils.bookmarks.update({ 229 guid: bmk.guid, 230 title: "New Title", 231 }); 232 233 await store.wipe(); 234 await engine.resetClient(); 235 // We don't sync via the service, so don't re-hit info/collections, so 236 // lastModified remaning at zero breaks things subtly... 237 engine.lastModified = null; 238 239 ping = await sync_engine_and_validate_telem(engine, false); 240 equal(ping.engines.length, 1); 241 equal(ping.engines[0].name, "bookmarks-buffered"); 242 equal(ping.engines[0].outgoing.length, 1); 243 ok(!!ping.engines[0].incoming); 244 } finally { 245 // Clean up. 246 await store.wipe(); 247 await cleanAndGo(engine, server); 248 } 249 }); 250 251 add_task(async function test_upload_failed() { 252 let collection = new ServerCollection(); 253 collection._wbos.flying = new ServerWBO("flying"); 254 255 let server = sync_httpd_setup({ 256 "/1.1/foo/storage/rotary": collection.handler(), 257 }); 258 259 await SyncTestingInfrastructure(server); 260 await configureIdentity({ username: "foo" }, server); 261 262 let engine = new RotaryEngine(Service); 263 engine._store.items = { 264 flying: "LNER Class A3 4472", 265 scotsman: "Flying Scotsman", 266 peppercorn: "Peppercorn Class", 267 }; 268 const FLYING_CHANGED = 12345; 269 const SCOTSMAN_CHANGED = 23456; 270 const PEPPERCORN_CHANGED = 34567; 271 await engine._tracker.addChangedID("flying", FLYING_CHANGED); 272 await engine._tracker.addChangedID("scotsman", SCOTSMAN_CHANGED); 273 await engine._tracker.addChangedID("peppercorn", PEPPERCORN_CHANGED); 274 275 let syncID = await engine.resetLocalSyncID(); 276 let meta_global = Service.recordManager.set( 277 engine.metaURL, 278 new WBORecord(engine.metaURL) 279 ); 280 meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; 281 282 try { 283 await engine.setLastSync(123); // needs to be non-zero so that tracker is queried 284 let changes = await engine._tracker.getChangedIDs(); 285 _( 286 `test_upload_failed: Rotary tracker contents at first sync: ${JSON.stringify( 287 changes 288 )}` 289 ); 290 engine.enabled = true; 291 let ping = await sync_engine_and_validate_telem(engine, true); 292 ok(!!ping); 293 equal(ping.engines.length, 1); 294 equal(ping.engines[0].incoming, null); 295 deepEqual(ping.engines[0].outgoing, [ 296 { 297 sent: 3, 298 failed: 2, 299 failedReasons: [ 300 { name: "scotsman", count: 1 }, 301 { name: "peppercorn", count: 1 }, 302 ], 303 }, 304 ]); 305 await engine.setLastSync(123); 306 307 changes = await engine._tracker.getChangedIDs(); 308 _( 309 `test_upload_failed: Rotary tracker contents at second sync: ${JSON.stringify( 310 changes 311 )}` 312 ); 313 ping = await sync_engine_and_validate_telem(engine, true); 314 ok(!!ping); 315 equal(ping.engines.length, 1); 316 deepEqual(ping.engines[0].outgoing, [ 317 { 318 sent: 2, 319 failed: 2, 320 failedReasons: [ 321 { name: "scotsman", count: 1 }, 322 { name: "peppercorn", count: 1 }, 323 ], 324 }, 325 ]); 326 } finally { 327 await cleanAndGo(engine, server); 328 await engine.finalize(); 329 } 330 }); 331 332 add_task(async function test_sync_partialUpload() { 333 let collection = new ServerCollection(); 334 let server = sync_httpd_setup({ 335 "/1.1/foo/storage/rotary": collection.handler(), 336 }); 337 await SyncTestingInfrastructure(server); 338 await generateNewKeys(Service.collectionKeys); 339 340 let engine = new RotaryEngine(Service); 341 await engine.setLastSync(123); 342 343 // Create a bunch of records (and server side handlers) 344 for (let i = 0; i < 234; i++) { 345 let id = "record-no-" + i; 346 engine._store.items[id] = "Record No. " + i; 347 await engine._tracker.addChangedID(id, i); 348 // Let two items in the first upload batch fail. 349 if (i != 23 && i != 42) { 350 collection.insert(id); 351 } 352 } 353 354 let syncID = await engine.resetLocalSyncID(); 355 let meta_global = Service.recordManager.set( 356 engine.metaURL, 357 new WBORecord(engine.metaURL) 358 ); 359 meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; 360 361 try { 362 let changes = await engine._tracker.getChangedIDs(); 363 _( 364 `test_sync_partialUpload: Rotary tracker contents at first sync: ${JSON.stringify( 365 changes 366 )}` 367 ); 368 engine.enabled = true; 369 let ping = await sync_engine_and_validate_telem(engine, true); 370 371 ok(!!ping); 372 ok(!ping.failureReason); 373 equal(ping.engines.length, 1); 374 equal(ping.engines[0].name, "rotary"); 375 ok(!ping.engines[0].incoming); 376 ok(!ping.engines[0].failureReason); 377 deepEqual(ping.engines[0].outgoing, [ 378 { 379 sent: 234, 380 failed: 2, 381 failedReasons: [ 382 { name: "record-no-23", count: 1 }, 383 { name: "record-no-42", count: 1 }, 384 ], 385 }, 386 ]); 387 collection.post = function () { 388 throw new Error("Failure"); 389 }; 390 391 engine._store.items["record-no-1000"] = "Record No. 1000"; 392 await engine._tracker.addChangedID("record-no-1000", 1000); 393 collection.insert("record-no-1000", 1000); 394 395 await engine.setLastSync(123); 396 ping = null; 397 398 changes = await engine._tracker.getChangedIDs(); 399 _( 400 `test_sync_partialUpload: Rotary tracker contents at second sync: ${JSON.stringify( 401 changes 402 )}` 403 ); 404 try { 405 // should throw 406 await sync_engine_and_validate_telem( 407 engine, 408 true, 409 errPing => (ping = errPing) 410 ); 411 } catch (e) {} 412 // It would be nice if we had a more descriptive error for this... 413 let uploadFailureError = { 414 name: "httperror", 415 code: 500, 416 }; 417 418 ok(!!ping); 419 deepEqual(ping.failureReason, uploadFailureError); 420 equal(ping.engines.length, 1); 421 equal(ping.engines[0].name, "rotary"); 422 deepEqual(ping.engines[0].incoming, { 423 failed: 1, 424 failedReasons: [{ name: "No ciphertext: nothing to decrypt?", count: 1 }], 425 }); 426 ok(!ping.engines[0].outgoing); 427 deepEqual(ping.engines[0].failureReason, uploadFailureError); 428 } finally { 429 await cleanAndGo(engine, server); 430 await engine.finalize(); 431 } 432 }); 433 434 add_task(async function test_generic_engine_fail() { 435 enableValidationPrefs(); 436 437 await Service.engineManager.register(SteamEngine); 438 let engine = Service.engineManager.get("steam"); 439 engine.enabled = true; 440 let server = await serverForFoo(engine); 441 await SyncTestingInfrastructure(server); 442 let e = new Error("generic failure message"); 443 engine._errToThrow = e; 444 445 try { 446 const changes = await engine._tracker.getChangedIDs(); 447 _( 448 `test_generic_engine_fail: Steam tracker contents: ${JSON.stringify( 449 changes 450 )}` 451 ); 452 await sync_and_validate_telem(ping => { 453 equal(ping.status.service, SYNC_FAILED_PARTIAL); 454 deepEqual(ping.engines.find(err => err.name === "steam").failureReason, { 455 name: "unexpectederror", 456 error: String(e), 457 }); 458 }); 459 } finally { 460 await cleanAndGo(engine, server); 461 await Service.engineManager.unregister(engine); 462 } 463 }); 464 465 add_task(async function test_engine_fail_weird_errors() { 466 enableValidationPrefs(); 467 await Service.engineManager.register(SteamEngine); 468 let engine = Service.engineManager.get("steam"); 469 engine.enabled = true; 470 let server = await serverForFoo(engine); 471 await SyncTestingInfrastructure(server); 472 try { 473 let msg = "Bad things happened!"; 474 engine._errToThrow = { message: msg }; 475 await sync_and_validate_telem(ping => { 476 equal(ping.status.service, SYNC_FAILED_PARTIAL); 477 deepEqual(ping.engines.find(err => err.name === "steam").failureReason, { 478 name: "unexpectederror", 479 error: "Bad things happened!", 480 }); 481 }); 482 let e = { msg }; 483 engine._errToThrow = e; 484 await sync_and_validate_telem(ping => { 485 deepEqual(ping.engines.find(err => err.name === "steam").failureReason, { 486 name: "unexpectederror", 487 error: JSON.stringify(e), 488 }); 489 }); 490 } finally { 491 await cleanAndGo(engine, server); 492 Service.engineManager.unregister(engine); 493 } 494 }); 495 496 add_task(async function test_overrideTelemetryName() { 497 enableValidationPrefs(["steam"]); 498 499 await Service.engineManager.register(SteamEngine); 500 let engine = Service.engineManager.get("steam"); 501 engine.overrideTelemetryName = "steam-but-better"; 502 engine.enabled = true; 503 let server = await serverForFoo(engine); 504 await SyncTestingInfrastructure(server); 505 506 const problemsToReport = [ 507 { name: "someProblem", count: 123 }, 508 { name: "anotherProblem", count: 456 }, 509 ]; 510 511 try { 512 info("Sync with validation problems"); 513 engine.problemsToReport = problemsToReport; 514 await sync_and_validate_telem(ping => { 515 let enginePing = ping.engines.find(e => e.name === "steam-but-better"); 516 ok(enginePing); 517 ok(!ping.engines.find(e => e.name === "steam")); 518 delete enginePing.validation.took; // can't compare real times. 519 deepEqual( 520 enginePing.validation, 521 { 522 version: 1, 523 checked: 0, 524 problems: problemsToReport, 525 }, 526 "Should include validation report with overridden name" 527 ); 528 }); 529 530 info("Sync without validation problems"); 531 engine.problemsToReport = null; 532 await sync_and_validate_telem(ping => { 533 let enginePing = ping.engines.find(e => e.name === "steam-but-better"); 534 ok(enginePing); 535 ok(!ping.engines.find(e => e.name === "steam")); 536 ok( 537 !enginePing.validation, 538 "Should not include validation report when there are no problems" 539 ); 540 }); 541 } finally { 542 await cleanAndGo(engine, server); 543 await Service.engineManager.unregister(engine); 544 } 545 }); 546 547 add_task(async function test_engine_fail_ioerror() { 548 enableValidationPrefs(); 549 550 await Service.engineManager.register(SteamEngine); 551 let engine = Service.engineManager.get("steam"); 552 engine.enabled = true; 553 let server = await serverForFoo(engine); 554 await SyncTestingInfrastructure(server); 555 // create an IOError to re-throw as part of Sync. 556 try { 557 // (Note that fakeservices.js has replaced Utils.jsonMove etc, but for 558 // this test we need the real one so we get real exceptions from the 559 // filesystem.) 560 await Utils._real_jsonMove("file-does-not-exist", "anything", {}); 561 } catch (ex) { 562 engine._errToThrow = ex; 563 } 564 ok(engine._errToThrow, "expecting exception"); 565 566 try { 567 const changes = await engine._tracker.getChangedIDs(); 568 _( 569 `test_engine_fail_ioerror: Steam tracker contents: ${JSON.stringify( 570 changes 571 )}` 572 ); 573 await sync_and_validate_telem(ping => { 574 equal(ping.status.service, SYNC_FAILED_PARTIAL); 575 let failureReason = ping.engines.find( 576 e => e.name === "steam" 577 ).failureReason; 578 equal(failureReason.name, "unexpectederror"); 579 // ensure the profile dir in the exception message has been stripped. 580 ok( 581 !failureReason.error.includes(PathUtils.profileDir), 582 failureReason.error 583 ); 584 ok(failureReason.error.includes("[profileDir]"), failureReason.error); 585 }); 586 } finally { 587 await cleanAndGo(engine, server); 588 await Service.engineManager.unregister(engine); 589 } 590 }); 591 592 add_task(async function test_error_detections() { 593 let telem = get_sync_test_telemetry(); 594 595 // Non-network NS_ERROR_ codes get their own category. 596 Assert.deepEqual( 597 telem.transformError(Components.Exception("", Cr.NS_ERROR_FAILURE)), 598 { name: "nserror", code: Cr.NS_ERROR_FAILURE } 599 ); 600 601 // Some NS_ERROR_ code in the "network" module are treated as http errors. 602 Assert.deepEqual( 603 telem.transformError(Components.Exception("", Cr.NS_ERROR_UNKNOWN_HOST)), 604 { name: "httperror", code: Cr.NS_ERROR_UNKNOWN_HOST } 605 ); 606 // Some NS_ERROR_ABORT is treated as network by our telemetry. 607 Assert.deepEqual( 608 telem.transformError(Components.Exception("", Cr.NS_ERROR_ABORT)), 609 { name: "httperror", code: Cr.NS_ERROR_ABORT } 610 ); 611 }); 612 613 add_task(async function test_clean_urls() { 614 enableValidationPrefs(); 615 616 await Service.engineManager.register(SteamEngine); 617 let engine = Service.engineManager.get("steam"); 618 engine.enabled = true; 619 let server = await serverForFoo(engine); 620 await SyncTestingInfrastructure(server); 621 engine._errToThrow = new TypeError( 622 "http://www.google .com is not a valid URL." 623 ); 624 625 try { 626 const changes = await engine._tracker.getChangedIDs(); 627 _(`test_clean_urls: Steam tracker contents: ${JSON.stringify(changes)}`); 628 await sync_and_validate_telem(ping => { 629 equal(ping.status.service, SYNC_FAILED_PARTIAL); 630 let failureReason = ping.engines.find( 631 e => e.name === "steam" 632 ).failureReason; 633 equal(failureReason.name, "unexpectederror"); 634 equal(failureReason.error, "<URL> is not a valid URL."); 635 }); 636 // Handle other errors that include urls. 637 engine._errToThrow = 638 "Other error message that includes some:url/foo/bar/ in it."; 639 await sync_and_validate_telem(ping => { 640 equal(ping.status.service, SYNC_FAILED_PARTIAL); 641 let failureReason = ping.engines.find( 642 e => e.name === "steam" 643 ).failureReason; 644 equal(failureReason.name, "unexpectederror"); 645 equal( 646 failureReason.error, 647 "Other error message that includes <URL> in it." 648 ); 649 }); 650 } finally { 651 await cleanAndGo(engine, server); 652 await Service.engineManager.unregister(engine); 653 } 654 }); 655 656 // Test sanitizing guid-related errors with the pattern of <guid: {guid}> 657 add_task(async function test_sanitize_bookmarks_guid() { 658 let { ErrorSanitizer } = ChromeUtils.importESModule( 659 "resource://services-sync/telemetry.sys.mjs" 660 ); 661 662 for (let [original, expected] of [ 663 [ 664 "Can't insert Bookmark <guid: sknD84IdnSY2> into Folder <guid: odfninDdi93_3>", 665 "Can't insert Bookmark <GUID> into Folder <GUID>", 666 ], 667 [ 668 "Merge Error: Item <guid: H6fmPA16gZs9> can't contain itself", 669 "Merge Error: Item <GUID> can't contain itself", 670 ], 671 ]) { 672 const sanitized = ErrorSanitizer.cleanErrorMessage(original); 673 Assert.equal(sanitized, expected); 674 } 675 }); 676 677 // Test sanitization of some hard-coded error strings. 678 add_task(async function test_clean_errors() { 679 let { ErrorSanitizer } = ChromeUtils.importESModule( 680 "resource://services-sync/telemetry.sys.mjs" 681 ); 682 683 for (let [message, name, expected] of [ 684 [ 685 `Could not open the file at ${PathUtils.join( 686 PathUtils.profileDir, 687 "weave", 688 "addonsreconciler.json" 689 )} for writing`, 690 "NotFoundError", 691 "OS error [File/Path not found] Could not open the file at [profileDir]/weave/addonsreconciler.json for writing", 692 ], 693 [ 694 `Could not get info for the file at ${PathUtils.join( 695 PathUtils.profileDir, 696 "weave", 697 "addonsreconciler.json" 698 )}`, 699 "NotAllowedError", 700 "OS error [Permission denied] Could not get info for the file at [profileDir]/weave/addonsreconciler.json", 701 ], 702 ]) { 703 const error = new DOMException(message, name); 704 const sanitized = ErrorSanitizer.cleanErrorMessage(message, error); 705 Assert.equal(sanitized, expected); 706 } 707 }); 708 709 // Arrange for a sync to hit a "real" OS error during a sync and make sure it's sanitized. 710 add_task(async function test_clean_real_os_error() { 711 enableValidationPrefs(); 712 713 // Simulate a real error. 714 await Service.engineManager.register(SteamEngine); 715 let engine = Service.engineManager.get("steam"); 716 engine.enabled = true; 717 let server = await serverForFoo(engine); 718 await SyncTestingInfrastructure(server); 719 let path = PathUtils.join(PathUtils.profileDir, "no", "such", "path.json"); 720 try { 721 await IOUtils.readJSON(path); 722 throw new Error("should fail to read the file"); 723 } catch (ex) { 724 engine._errToThrow = ex; 725 } 726 727 try { 728 const changes = await engine._tracker.getChangedIDs(); 729 _(`test_clean_urls: Steam tracker contents: ${JSON.stringify(changes)}`); 730 await sync_and_validate_telem(ping => { 731 equal(ping.status.service, SYNC_FAILED_PARTIAL); 732 let failureReason = ping.engines.find( 733 e => e.name === "steam" 734 ).failureReason; 735 equal(failureReason.name, "unexpectederror"); 736 equal( 737 failureReason.error, 738 "OS error [File/Path not found] Could not open `[profileDir]/no/such/path.json': file does not exist" 739 ); 740 }); 741 } finally { 742 await cleanAndGo(engine, server); 743 await Service.engineManager.unregister(engine); 744 } 745 }); 746 747 add_task(async function test_initial_sync_engines() { 748 enableValidationPrefs(); 749 750 await Service.engineManager.register(SteamEngine); 751 let engine = Service.engineManager.get("steam"); 752 engine.enabled = true; 753 // These are the only ones who actually have things to sync at startup. 754 let telemetryEngineNames = ["clients", "prefs", "tabs", "bookmarks-buffered"]; 755 let server = await serverForEnginesWithKeys( 756 { foo: "password" }, 757 ["bookmarks", "prefs", "tabs"].map(name => Service.engineManager.get(name)) 758 ); 759 await SyncTestingInfrastructure(server); 760 try { 761 const changes = await engine._tracker.getChangedIDs(); 762 _( 763 `test_initial_sync_engines: Steam tracker contents: ${JSON.stringify( 764 changes 765 )}` 766 ); 767 let ping = await wait_for_ping(() => Service.sync(), true); 768 769 equal(ping.engines.find(e => e.name === "clients").outgoing[0].sent, 1); 770 equal(ping.engines.find(e => e.name === "tabs").outgoing[0].sent, 1); 771 772 // for the rest we don't care about specifics 773 for (let e of ping.engines) { 774 if (!telemetryEngineNames.includes(e.name)) { 775 continue; 776 } 777 greaterOrEqual(e.took, 0); 778 ok(!!e.outgoing); 779 equal(e.outgoing.length, 1); 780 notEqual(e.outgoing[0].sent, undefined); 781 equal(e.outgoing[0].failed, undefined); 782 equal(e.outgoing[0].failedReasons, undefined); 783 } 784 } finally { 785 await cleanAndGo(engine, server); 786 await Service.engineManager.unregister(engine); 787 } 788 }); 789 790 add_task(async function test_nserror() { 791 enableValidationPrefs(); 792 793 await Service.engineManager.register(SteamEngine); 794 let engine = Service.engineManager.get("steam"); 795 engine.enabled = true; 796 let server = await serverForFoo(engine); 797 await SyncTestingInfrastructure(server); 798 engine._errToThrow = Components.Exception( 799 "NS_ERROR_UNKNOWN_HOST", 800 Cr.NS_ERROR_UNKNOWN_HOST 801 ); 802 try { 803 const changes = await engine._tracker.getChangedIDs(); 804 _(`test_nserror: Steam tracker contents: ${JSON.stringify(changes)}`); 805 await sync_and_validate_telem(ping => { 806 deepEqual(ping.status, { 807 service: SYNC_FAILED_PARTIAL, 808 sync: LOGIN_FAILED_NETWORK_ERROR, 809 }); 810 let enginePing = ping.engines.find(e => e.name === "steam"); 811 deepEqual(enginePing.failureReason, { 812 name: "httperror", 813 code: Cr.NS_ERROR_UNKNOWN_HOST, 814 }); 815 }); 816 } finally { 817 await cleanAndGo(engine, server); 818 await Service.engineManager.unregister(engine); 819 } 820 }); 821 822 add_task(async function test_sync_why() { 823 enableValidationPrefs(); 824 825 await Service.engineManager.register(SteamEngine); 826 let engine = Service.engineManager.get("steam"); 827 engine.enabled = true; 828 let server = await serverForFoo(engine); 829 await SyncTestingInfrastructure(server); 830 let e = new Error("generic failure message"); 831 engine._errToThrow = e; 832 833 try { 834 const changes = await engine._tracker.getChangedIDs(); 835 _( 836 `test_generic_engine_fail: Steam tracker contents: ${JSON.stringify( 837 changes 838 )}` 839 ); 840 let ping = await wait_for_ping( 841 () => Service.sync({ why: "user" }), 842 true, 843 false 844 ); 845 _(JSON.stringify(ping)); 846 equal(ping.why, "user"); 847 } finally { 848 await cleanAndGo(engine, server); 849 await Service.engineManager.unregister(engine); 850 } 851 }); 852 853 add_task(async function test_discarding() { 854 enableValidationPrefs(); 855 856 let helper = track_collections_helper(); 857 let upd = helper.with_updated_collection; 858 let telem = get_sync_test_telemetry(); 859 telem.maxPayloadCount = 2; 860 telem.submissionInterval = Infinity; 861 let oldSubmit = telem.submit; 862 863 let server; 864 try { 865 let handlers = { 866 "/1.1/johndoe/info/collections": helper.handler, 867 "/1.1/johndoe/storage/crypto/keys": upd( 868 "crypto", 869 new ServerWBO("keys").handler() 870 ), 871 "/1.1/johndoe/storage/meta/global": upd( 872 "meta", 873 new ServerWBO("global").handler() 874 ), 875 }; 876 877 let collections = [ 878 "clients", 879 "bookmarks", 880 "forms", 881 "history", 882 "passwords", 883 "prefs", 884 "tabs", 885 ]; 886 887 for (let coll of collections) { 888 handlers["/1.1/johndoe/storage/" + coll] = upd( 889 coll, 890 new ServerCollection({}, true).handler() 891 ); 892 } 893 894 server = httpd_setup(handlers); 895 await configureIdentity({ username: "johndoe" }, server); 896 telem.submit = p => 897 ok( 898 false, 899 "Submitted telemetry ping when we should not have" + JSON.stringify(p) 900 ); 901 902 for (let i = 0; i < 5; ++i) { 903 await Service.sync(); 904 } 905 telem.submit = oldSubmit; 906 telem.submissionInterval = -1; 907 let ping = await wait_for_ping(() => Service.sync(), true, true); // with this we've synced 6 times 908 equal(ping.syncs.length, 2); 909 equal(ping.discarded, 4); 910 } finally { 911 telem.maxPayloadCount = 500; 912 telem.submissionInterval = -1; 913 telem.submit = oldSubmit; 914 if (server) { 915 await promiseStopServer(server); 916 } 917 } 918 }); 919 920 add_task(async function test_submit_interval() { 921 let telem = get_sync_test_telemetry(); 922 let oldSubmit = telem.submit; 923 let numSubmissions = 0; 924 telem.submit = function () { 925 numSubmissions += 1; 926 }; 927 928 function notify(what, data = null) { 929 Svc.Obs.notify(what, JSON.stringify(data)); 930 } 931 932 try { 933 // submissionInterval is set such that each sync should submit 934 notify("weave:service:sync:start", { why: "testing" }); 935 notify("weave:service:sync:finish"); 936 Assert.equal(numSubmissions, 1, "should submit this ping due to interval"); 937 938 // As should each event outside of a sync. 939 Service.recordTelemetryEvent("object", "method"); 940 Assert.equal(numSubmissions, 2); 941 942 // But events while we are syncing should not. 943 notify("weave:service:sync:start", { why: "testing" }); 944 Service.recordTelemetryEvent("object", "method"); 945 Assert.equal(numSubmissions, 2, "no submission for this event"); 946 notify("weave:service:sync:finish"); 947 Assert.equal(numSubmissions, 3, "was submitted after sync finish"); 948 } finally { 949 telem.submit = oldSubmit; 950 } 951 }); 952 953 add_task(async function test_no_foreign_engines_in_error_ping() { 954 enableValidationPrefs(); 955 956 await Service.engineManager.register(BogusEngine); 957 let engine = Service.engineManager.get("bogus"); 958 engine.enabled = true; 959 let server = await serverForFoo(engine); 960 engine._errToThrow = new Error("Oh no!"); 961 await SyncTestingInfrastructure(server); 962 try { 963 await sync_and_validate_telem(ping => { 964 equal(ping.status.service, SYNC_FAILED_PARTIAL); 965 ok(ping.engines.every(e => e.name !== "bogus")); 966 }); 967 } finally { 968 await cleanAndGo(engine, server); 969 await Service.engineManager.unregister(engine); 970 } 971 }); 972 973 add_task(async function test_no_foreign_engines_in_success_ping() { 974 enableValidationPrefs(); 975 976 await Service.engineManager.register(BogusEngine); 977 let engine = Service.engineManager.get("bogus"); 978 engine.enabled = true; 979 let server = await serverForFoo(engine); 980 981 await SyncTestingInfrastructure(server); 982 try { 983 await sync_and_validate_telem(ping => { 984 ok(ping.engines.every(e => e.name !== "bogus")); 985 }); 986 } finally { 987 await cleanAndGo(engine, server); 988 await Service.engineManager.unregister(engine); 989 } 990 }); 991 992 add_task(async function test_events() { 993 enableValidationPrefs(); 994 995 await Service.engineManager.register(BogusEngine); 996 let engine = Service.engineManager.get("bogus"); 997 engine.enabled = true; 998 let server = await serverForFoo(engine); 999 1000 await SyncTestingInfrastructure(server); 1001 1002 let telem = get_sync_test_telemetry(); 1003 telem.submissionInterval = Infinity; 1004 1005 try { 1006 let serverTime = Resource.serverTime; 1007 Service.recordTelemetryEvent("object", "method", "value", { foo: "bar" }); 1008 let ping = await wait_for_ping(() => Service.sync(), true, true); 1009 equal(ping.events.length, 1); 1010 let [timestamp, category, method, object, value, extra] = ping.events[0]; 1011 ok(typeof timestamp == "number" && timestamp > 0); // timestamp. 1012 equal(category, "sync"); 1013 equal(method, "method"); 1014 equal(object, "object"); 1015 equal(value, "value"); 1016 deepEqual(extra, { foo: "bar", serverTime: String(serverTime) }); 1017 ping = await wait_for_ping( 1018 () => { 1019 // Test with optional values. 1020 Service.recordTelemetryEvent("object", "method"); 1021 }, 1022 false, 1023 true 1024 ); 1025 equal(ping.events.length, 1); 1026 equal(ping.events[0].length, 4); 1027 1028 ping = await wait_for_ping( 1029 () => { 1030 Service.recordTelemetryEvent("object", "method", "extra"); 1031 }, 1032 false, 1033 true 1034 ); 1035 equal(ping.events.length, 1); 1036 equal(ping.events[0].length, 5); 1037 1038 ping = await wait_for_ping( 1039 () => { 1040 Service.recordTelemetryEvent("object", "method", undefined, { 1041 foo: "bar", 1042 }); 1043 }, 1044 false, 1045 true 1046 ); 1047 equal(ping.events.length, 1); 1048 equal(ping.events[0].length, 6); 1049 [timestamp, category, method, object, value, extra] = ping.events[0]; 1050 equal(value, null); 1051 1052 // Fake a submission due to shutdown. 1053 ping = await wait_for_ping( 1054 () => { 1055 telem.submissionInterval = Infinity; 1056 Service.recordTelemetryEvent("object", "method", undefined, { 1057 foo: "bar", 1058 }); 1059 telem.finish("shutdown"); 1060 }, 1061 false, 1062 true 1063 ); 1064 equal(ping.syncs.length, 0); 1065 equal(ping.events.length, 1); 1066 equal(ping.events[0].length, 6); 1067 } finally { 1068 await cleanAndGo(engine, server); 1069 await Service.engineManager.unregister(engine); 1070 } 1071 }); 1072 1073 add_task(async function test_histograms() { 1074 enableValidationPrefs(); 1075 1076 await Service.engineManager.register(BogusEngine); 1077 let engine = Service.engineManager.get("bogus"); 1078 engine.enabled = true; 1079 let server = await serverForFoo(engine); 1080 1081 await SyncTestingInfrastructure(server); 1082 try { 1083 let histId = "TELEMETRY_TEST_LINEAR"; 1084 Services.obs.notifyObservers(null, "weave:telemetry:histogram", histId); 1085 let ping = await wait_for_ping(() => Service.sync(), true, true); 1086 equal(Object.keys(ping.histograms).length, 1); 1087 equal(ping.histograms[histId].sum, 0); 1088 equal(ping.histograms[histId].histogram_type, 1); 1089 } finally { 1090 await cleanAndGo(engine, server); 1091 await Service.engineManager.unregister(engine); 1092 } 1093 }); 1094 1095 add_task(async function test_invalid_events() { 1096 enableValidationPrefs(); 1097 1098 await Service.engineManager.register(BogusEngine); 1099 let engine = Service.engineManager.get("bogus"); 1100 engine.enabled = true; 1101 let server = await serverForFoo(engine); 1102 1103 async function checkNotRecorded(...args) { 1104 Service.recordTelemetryEvent.call(args); 1105 let ping = await wait_for_ping(() => Service.sync(), false, true); 1106 equal(ping.events, undefined); 1107 } 1108 1109 await SyncTestingInfrastructure(server); 1110 try { 1111 let long21 = "l".repeat(21); 1112 let long81 = "l".repeat(81); 1113 let long86 = "l".repeat(86); 1114 await checkNotRecorded("object"); 1115 await checkNotRecorded("object", 2); 1116 await checkNotRecorded(2, "method"); 1117 await checkNotRecorded("object", "method", 2); 1118 await checkNotRecorded("object", "method", "value", 2); 1119 await checkNotRecorded("object", "method", "value", { foo: 2 }); 1120 await checkNotRecorded(long21, "method", "value"); 1121 await checkNotRecorded("object", long21, "value"); 1122 await checkNotRecorded("object", "method", long81); 1123 let badextra = {}; 1124 badextra[long21] = "x"; 1125 await checkNotRecorded("object", "method", "value", badextra); 1126 badextra = { x: long86 }; 1127 await checkNotRecorded("object", "method", "value", badextra); 1128 for (let i = 0; i < 10; i++) { 1129 badextra["name" + i] = "x"; 1130 } 1131 await checkNotRecorded("object", "method", "value", badextra); 1132 } finally { 1133 await cleanAndGo(engine, server); 1134 await Service.engineManager.unregister(engine); 1135 } 1136 }); 1137 1138 add_task(async function test_no_ping_for_self_hosters() { 1139 enableValidationPrefs(); 1140 1141 let telem = get_sync_test_telemetry(); 1142 let oldSubmit = telem.submit; 1143 1144 await Service.engineManager.register(BogusEngine); 1145 let engine = Service.engineManager.get("bogus"); 1146 engine.enabled = true; 1147 let server = await serverForFoo(engine); 1148 1149 await SyncTestingInfrastructure(server); 1150 try { 1151 let submitPromise = new Promise(resolve => { 1152 telem.submit = function () { 1153 let result = oldSubmit.apply(this, arguments); 1154 resolve(result); 1155 }; 1156 }); 1157 await Service.sync(); 1158 let pingSubmitted = await submitPromise; 1159 // The Sync testing infrastructure already sets up a custom token server, 1160 // so we don't need to do anything to simulate a self-hosted user. 1161 ok(!pingSubmitted, "Should not submit ping with custom token server URL"); 1162 } finally { 1163 telem.submit = oldSubmit; 1164 await cleanAndGo(engine, server); 1165 await Service.engineManager.unregister(engine); 1166 } 1167 }); 1168 1169 add_task(async function test_fxa_device_telem() { 1170 let t = get_sync_test_telemetry(); 1171 let syncEnabled = true; 1172 let oldGetClientsEngineRecords = t.getClientsEngineRecords; 1173 let oldGetFxaDevices = t.getFxaDevices; 1174 let oldSyncIsEnabled = t.syncIsEnabled; 1175 let oldSanitizeFxaDeviceId = t.sanitizeFxaDeviceId; 1176 t.syncIsEnabled = () => syncEnabled; 1177 t.sanitizeFxaDeviceId = id => `So clean: ${id}`; 1178 try { 1179 let keep0 = Utils.makeGUID(); 1180 let keep1 = Utils.makeGUID(); 1181 let keep2 = Utils.makeGUID(); 1182 let curdev = Utils.makeGUID(); 1183 1184 let keep1Sync = Utils.makeGUID(); 1185 let keep2Sync = Utils.makeGUID(); 1186 let curdevSync = Utils.makeGUID(); 1187 let fxaDevices = [ 1188 { 1189 id: curdev, 1190 isCurrentDevice: true, 1191 lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 1, 1192 pushEndpointExpired: false, 1193 type: "desktop", 1194 name: "current device", 1195 }, 1196 { 1197 id: keep0, 1198 isCurrentDevice: false, 1199 lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 10, 1200 pushEndpointExpired: false, 1201 type: "mobile", 1202 name: "dupe", 1203 }, 1204 // Valid 2 1205 { 1206 id: keep1, 1207 isCurrentDevice: false, 1208 lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 1, 1209 pushEndpointExpired: false, 1210 type: "desktop", 1211 name: "valid2", 1212 }, 1213 // Valid 3 1214 { 1215 id: keep2, 1216 isCurrentDevice: false, 1217 lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 5, 1218 pushEndpointExpired: false, 1219 type: "desktop", 1220 name: "valid3", 1221 }, 1222 ]; 1223 let clientInfo = [ 1224 { 1225 id: keep1Sync, 1226 fxaDeviceId: keep1, 1227 os: "Windows 30", 1228 version: "Firefox 1 million", 1229 }, 1230 { 1231 id: keep2Sync, 1232 fxaDeviceId: keep2, 1233 os: "firefox, but an os", 1234 verison: "twelve", 1235 }, 1236 { 1237 id: Utils.makeGUID(), 1238 fxaDeviceId: null, 1239 os: "apparently ios used to keep write these IDs as null.", 1240 version: "Doesn't seem to anymore", 1241 }, 1242 { 1243 id: curdevSync, 1244 fxaDeviceId: curdev, 1245 os: "emacs", 1246 version: "22", 1247 }, 1248 { 1249 id: Utils.makeGUID(), 1250 fxaDeviceId: Utils.makeGUID(), 1251 os: "not part of the fxa device set at all", 1252 version: "foo bar baz", 1253 }, 1254 // keep0 intententionally omitted. 1255 ]; 1256 t.getClientsEngineRecords = () => clientInfo; 1257 let devInfo = t.updateFxaDevices(fxaDevices); 1258 equal(devInfo.deviceID, t.sanitizeFxaDeviceId(curdev)); 1259 for (let d of devInfo.devices) { 1260 ok(d.id.startsWith("So clean:")); 1261 if (d.syncID) { 1262 ok(d.syncID.startsWith("So clean:")); 1263 } 1264 } 1265 equal(devInfo.devices.length, 4); 1266 let k0 = devInfo.devices.find(d => d.id == t.sanitizeFxaDeviceId(keep0)); 1267 let k1 = devInfo.devices.find(d => d.id == t.sanitizeFxaDeviceId(keep1)); 1268 let k2 = devInfo.devices.find(d => d.id == t.sanitizeFxaDeviceId(keep2)); 1269 1270 deepEqual(k0, { 1271 id: t.sanitizeFxaDeviceId(keep0), 1272 type: "mobile", 1273 os: undefined, 1274 version: undefined, 1275 syncID: undefined, 1276 }); 1277 deepEqual(k1, { 1278 id: t.sanitizeFxaDeviceId(keep1), 1279 type: "desktop", 1280 os: clientInfo[0].os, 1281 version: clientInfo[0].version, 1282 syncID: t.sanitizeFxaDeviceId(keep1Sync), 1283 }); 1284 deepEqual(k2, { 1285 id: t.sanitizeFxaDeviceId(keep2), 1286 type: "desktop", 1287 os: clientInfo[1].os, 1288 version: clientInfo[1].version, 1289 syncID: t.sanitizeFxaDeviceId(keep2Sync), 1290 }); 1291 let newCurId = Utils.makeGUID(); 1292 // Update the ID 1293 fxaDevices[0].id = newCurId; 1294 1295 let keep3 = Utils.makeGUID(); 1296 fxaDevices.push({ 1297 id: keep3, 1298 isCurrentDevice: false, 1299 lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 1, 1300 pushEndpointExpired: false, 1301 type: "desktop", 1302 name: "valid 4", 1303 }); 1304 devInfo = t.updateFxaDevices(fxaDevices); 1305 1306 let afterSubmit = [keep0, keep1, keep2, keep3, newCurId] 1307 .map(id => t.sanitizeFxaDeviceId(id)) 1308 .sort(); 1309 deepEqual(devInfo.devices.map(d => d.id).sort(), afterSubmit); 1310 1311 // Reset this, as our override doesn't check for sync being enabled. 1312 t.sanitizeFxaDeviceId = oldSanitizeFxaDeviceId; 1313 syncEnabled = false; 1314 fxAccounts.telemetry._setHashedUID(false); 1315 devInfo = t.updateFxaDevices(fxaDevices); 1316 equal(devInfo.deviceID, undefined); 1317 equal(devInfo.devices.length, 5); 1318 for (let d of devInfo.devices) { 1319 equal(d.os, undefined); 1320 equal(d.version, undefined); 1321 equal(d.syncID, undefined); 1322 // Type should still be present. 1323 notEqual(d.type, undefined); 1324 } 1325 } finally { 1326 t.getClientsEngineRecords = oldGetClientsEngineRecords; 1327 t.getFxaDevices = oldGetFxaDevices; 1328 t.syncIsEnabled = oldSyncIsEnabled; 1329 t.sanitizeFxaDeviceId = oldSanitizeFxaDeviceId; 1330 } 1331 }); 1332 1333 add_task(async function test_sanitize_fxa_device_id() { 1334 let t = get_sync_test_telemetry(); 1335 fxAccounts.telemetry._setHashedUID(false); 1336 sinon.stub(t, "syncIsEnabled").callsFake(() => true); 1337 const rawDeviceId = "raw one two three"; 1338 try { 1339 equal(t.sanitizeFxaDeviceId(rawDeviceId), null); 1340 fxAccounts.telemetry._setHashedUID("mock uid"); 1341 const sanitizedDeviceId = t.sanitizeFxaDeviceId(rawDeviceId); 1342 ok(sanitizedDeviceId); 1343 notEqual(sanitizedDeviceId, rawDeviceId); 1344 } finally { 1345 t.syncIsEnabled.restore(); 1346 fxAccounts.telemetry._setHashedUID(false); 1347 } 1348 }); 1349 1350 add_task(async function test_no_node_type() { 1351 let server = sync_httpd_setup({}); 1352 await configureIdentity(null, server); 1353 1354 await sync_and_validate_telem(ping => { 1355 Assert.strictEqual(ping.syncNodeType, undefined); 1356 }, true); 1357 await promiseStopServer(server); 1358 }); 1359 1360 add_task(async function test_node_type() { 1361 Service.identity.logout(); 1362 let server = sync_httpd_setup({}); 1363 await configureIdentity({ node_type: "the-node-type" }, server); 1364 1365 await sync_and_validate_telem(ping => { 1366 equal(ping.syncNodeType, "the-node-type"); 1367 }, true); 1368 await promiseStopServer(server); 1369 }); 1370 1371 add_task(async function test_node_type_change() { 1372 let pingPromise = wait_for_pings(2); 1373 1374 Service.identity.logout(); 1375 let server = sync_httpd_setup({}); 1376 await configureIdentity({ node_type: "first-node-type" }, server); 1377 // Default to submitting each hour - we should still submit on node change. 1378 let telem = get_sync_test_telemetry(); 1379 telem.submissionInterval = 60 * 60 * 1000; 1380 // reset the node type from previous test or our first sync will submit. 1381 telem.lastSyncNodeType = null; 1382 // do 2 syncs with the same node type. 1383 await Service.sync(); 1384 await Service.sync(); 1385 // then another with a different node type. 1386 Service.identity.logout(); 1387 await configureIdentity({ node_type: "second-node-type" }, server); 1388 await Service.sync(); 1389 telem.finish(); 1390 1391 let pings = await pingPromise; 1392 equal(pings.length, 2); 1393 equal(pings[0].syncs.length, 2, "2 syncs in first ping"); 1394 equal(pings[0].syncNodeType, "first-node-type"); 1395 equal(pings[1].syncs.length, 1, "1 sync in second ping"); 1396 equal(pings[1].syncNodeType, "second-node-type"); 1397 await promiseStopServer(server); 1398 }); 1399 1400 add_task(async function test_ids() { 1401 let telem = get_sync_test_telemetry(); 1402 Assert.ok(!telem._shouldSubmitForDataChange()); 1403 fxAccounts.telemetry._setHashedUID("new_uid"); 1404 Assert.ok(telem._shouldSubmitForDataChange()); 1405 telem.maybeSubmitForDataChange(); 1406 // now it's been submitted the new uid is current. 1407 Assert.ok(!telem._shouldSubmitForDataChange()); 1408 }); 1409 1410 add_task(async function test_deletion_request_ping() { 1411 async function assertRecordedSyncDeviceID(expected) { 1412 // The scalar gets updated asynchronously, so wait a tick before checking. 1413 await Promise.resolve(); 1414 const scalars = 1415 Services.telemetry.getSnapshotForScalars("deletion-request").parent || {}; 1416 equal(scalars["deletion.request.sync_device_id"], expected); 1417 } 1418 1419 const MOCK_HASHED_UID = "00112233445566778899aabbccddeeff"; 1420 const MOCK_DEVICE_ID1 = "ffeeddccbbaa99887766554433221100"; 1421 const MOCK_DEVICE_ID2 = "aabbccddeeff99887766554433221100"; 1422 1423 // Calculated by hand using SHA256(DEVICE_ID + HASHED_UID)[:32] 1424 const SANITIZED_DEVICE_ID1 = "dd7c845006df9baa1c6d756926519c8c"; 1425 const SANITIZED_DEVICE_ID2 = "0d06919a736fc029007e1786a091882c"; 1426 1427 let currentDeviceID = null; 1428 sinon.stub(fxAccounts.device, "getLocalId").callsFake(() => { 1429 return Promise.resolve(currentDeviceID); 1430 }); 1431 let telem = get_sync_test_telemetry(); 1432 sinon.stub(telem, "isProductionSyncUser").callsFake(() => true); 1433 fxAccounts.telemetry._setHashedUID(false); 1434 try { 1435 // The scalar should start out undefined, since no user is actually logged in. 1436 await assertRecordedSyncDeviceID(undefined); 1437 1438 // If we start up without knowing the hashed UID, it should stay undefined. 1439 telem.observe(null, "weave:service:ready"); 1440 await assertRecordedSyncDeviceID(undefined); 1441 1442 // But now let's say we've discovered the hashed UID from the server. 1443 fxAccounts.telemetry._setHashedUID(MOCK_HASHED_UID); 1444 currentDeviceID = MOCK_DEVICE_ID1; 1445 1446 // Now when we load up, we'll record the sync device id. 1447 telem.observe(null, "weave:service:ready"); 1448 await assertRecordedSyncDeviceID(SANITIZED_DEVICE_ID1); 1449 1450 // When the device-id changes we'll update it. 1451 currentDeviceID = MOCK_DEVICE_ID2; 1452 telem.observe(null, "fxaccounts:new_device_id"); 1453 await assertRecordedSyncDeviceID(SANITIZED_DEVICE_ID2); 1454 1455 // When the user signs out we'll clear it. 1456 telem.observe(null, "fxaccounts:onlogout"); 1457 await assertRecordedSyncDeviceID(""); 1458 } finally { 1459 fxAccounts.telemetry._setHashedUID(false); 1460 telem.isProductionSyncUser.restore(); 1461 fxAccounts.device.getLocalId.restore(); 1462 } 1463 });