test_push_service.js (11160B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Tests for the FxA push service. 7 8 /* eslint-disable mozilla/use-chromeutils-generateqi */ 9 10 const { 11 FXA_PUSH_SCOPE_ACCOUNT_UPDATE, 12 ONLOGOUT_NOTIFICATION, 13 ON_ACCOUNT_DESTROYED_NOTIFICATION, 14 ON_DEVICE_CONNECTED_NOTIFICATION, 15 ON_DEVICE_DISCONNECTED_NOTIFICATION, 16 ON_PASSWORD_CHANGED_NOTIFICATION, 17 ON_PASSWORD_RESET_NOTIFICATION, 18 ON_PROFILE_CHANGE_NOTIFICATION, 19 ON_PROFILE_UPDATED_NOTIFICATION, 20 ON_VERIFY_LOGIN_NOTIFICATION, 21 log, 22 } = ChromeUtils.importESModule( 23 "resource://gre/modules/FxAccountsCommon.sys.mjs" 24 ); 25 26 const { FxAccountsPushService } = ChromeUtils.importESModule( 27 "resource://gre/modules/FxAccountsPush.sys.mjs" 28 ); 29 30 XPCOMUtils.defineLazyServiceGetter( 31 this, 32 "PushService", 33 "@mozilla.org/push/Service;1", 34 Ci.nsIPushService 35 ); 36 37 initTestLogging("Trace"); 38 log.level = Log.Level.Trace; 39 40 const MOCK_ENDPOINT = "http://mochi.test:8888"; 41 42 // tests do not allow external connections, mock the PushService 43 let mockPushService = { 44 pushTopic: PushService.pushTopic, 45 subscriptionChangeTopic: PushService.subscriptionChangeTopic, 46 subscribe(scope, principal, cb) { 47 cb(Cr.NS_OK, { 48 endpoint: MOCK_ENDPOINT, 49 }); 50 }, 51 unsubscribe(scope, principal, cb) { 52 cb(Cr.NS_OK, true); 53 }, 54 }; 55 56 let mockFxAccounts = { 57 checkVerificationStatus() {}, 58 updateDeviceRegistration() {}, 59 }; 60 61 let mockLog = { 62 trace() {}, 63 debug() {}, 64 warn() {}, 65 error() {}, 66 }; 67 68 add_task(async function initialize() { 69 let pushService = new FxAccountsPushService(); 70 equal(pushService.initialize(), false); 71 }); 72 73 add_task(async function registerPushEndpointSuccess() { 74 let pushService = new FxAccountsPushService({ 75 pushService: mockPushService, 76 fxai: mockFxAccounts, 77 }); 78 79 let subscription = await pushService.registerPushEndpoint(); 80 equal(subscription.endpoint, MOCK_ENDPOINT); 81 }); 82 83 add_task(async function registerPushEndpointFailure() { 84 let failPushService = Object.assign(mockPushService, { 85 subscribe(scope, principal, cb) { 86 cb(Cr.NS_ERROR_ABORT); 87 }, 88 }); 89 90 let pushService = new FxAccountsPushService({ 91 pushService: failPushService, 92 fxai: mockFxAccounts, 93 }); 94 95 let subscription = await pushService.registerPushEndpoint(); 96 equal(subscription, null); 97 }); 98 99 add_task(async function unsubscribeSuccess() { 100 let pushService = new FxAccountsPushService({ 101 pushService: mockPushService, 102 fxai: mockFxAccounts, 103 }); 104 105 let result = await pushService.unsubscribe(); 106 equal(result, true); 107 }); 108 109 add_task(async function unsubscribeFailure() { 110 let failPushService = Object.assign(mockPushService, { 111 unsubscribe(scope, principal, cb) { 112 cb(Cr.NS_ERROR_ABORT); 113 }, 114 }); 115 116 let pushService = new FxAccountsPushService({ 117 pushService: failPushService, 118 fxai: mockFxAccounts, 119 }); 120 121 let result = await pushService.unsubscribe(); 122 equal(result, null); 123 }); 124 125 add_test(function observeLogout() { 126 let customLog = Object.assign(mockLog, { 127 trace(msg) { 128 if (msg === "FxAccountsPushService unsubscribe") { 129 // logout means we unsubscribe 130 run_next_test(); 131 } 132 }, 133 }); 134 135 let pushService = new FxAccountsPushService({ 136 pushService: mockPushService, 137 log: customLog, 138 }); 139 140 pushService.observe(null, ONLOGOUT_NOTIFICATION); 141 }); 142 143 add_test(function observePushTopicDeviceConnected() { 144 let msg = { 145 data: { 146 json: () => ({ 147 command: ON_DEVICE_CONNECTED_NOTIFICATION, 148 data: { 149 deviceName: "My phone", 150 }, 151 }), 152 }, 153 QueryInterface() { 154 return this; 155 }, 156 }; 157 let obs = (subject, topic) => { 158 Services.obs.removeObserver(obs, topic); 159 run_next_test(); 160 }; 161 Services.obs.addObserver(obs, ON_DEVICE_CONNECTED_NOTIFICATION); 162 163 let pushService = new FxAccountsPushService({ 164 pushService: mockPushService, 165 fxai: mockFxAccounts, 166 }); 167 168 pushService.observe( 169 msg, 170 mockPushService.pushTopic, 171 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 172 ); 173 }); 174 175 add_task(async function observePushTopicDeviceDisconnected_current_device() { 176 const deviceId = "bogusid"; 177 let msg = { 178 data: { 179 json: () => ({ 180 command: ON_DEVICE_DISCONNECTED_NOTIFICATION, 181 data: { 182 id: deviceId, 183 }, 184 }), 185 }, 186 QueryInterface() { 187 return this; 188 }, 189 }; 190 191 let signoutCalled = false; 192 let { FxAccounts } = ChromeUtils.importESModule( 193 "resource://gre/modules/FxAccounts.sys.mjs" 194 ); 195 const fxAccountsMock = new FxAccounts({ 196 newAccountState() { 197 return { 198 async getUserAccountData() { 199 return { device: { id: deviceId } }; 200 }, 201 }; 202 }, 203 signOut() { 204 signoutCalled = true; 205 }, 206 })._internal; 207 208 const deviceDisconnectedNotificationObserved = new Promise(resolve => { 209 Services.obs.addObserver(function obs(subject, topic, data) { 210 Services.obs.removeObserver(obs, topic); 211 equal(data, JSON.stringify({ isLocalDevice: true })); 212 resolve(); 213 }, ON_DEVICE_DISCONNECTED_NOTIFICATION); 214 }); 215 216 let pushService = new FxAccountsPushService({ 217 pushService: mockPushService, 218 fxai: fxAccountsMock, 219 }); 220 221 pushService.observe( 222 msg, 223 mockPushService.pushTopic, 224 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 225 ); 226 227 await deviceDisconnectedNotificationObserved; 228 ok(signoutCalled); 229 }); 230 231 add_task(async function observePushTopicDeviceDisconnected_another_device() { 232 const deviceId = "bogusid"; 233 let msg = { 234 data: { 235 json: () => ({ 236 command: ON_DEVICE_DISCONNECTED_NOTIFICATION, 237 data: { 238 id: deviceId, 239 }, 240 }), 241 }, 242 QueryInterface() { 243 return this; 244 }, 245 }; 246 247 let signoutCalled = false; 248 let { FxAccounts } = ChromeUtils.importESModule( 249 "resource://gre/modules/FxAccounts.sys.mjs" 250 ); 251 const fxAccountsMock = new FxAccounts({ 252 newAccountState() { 253 return { 254 async getUserAccountData() { 255 return { device: { id: "thelocaldevice" } }; 256 }, 257 }; 258 }, 259 signOut() { 260 signoutCalled = true; 261 }, 262 })._internal; 263 264 const deviceDisconnectedNotificationObserved = new Promise(resolve => { 265 Services.obs.addObserver(function obs(subject, topic, data) { 266 Services.obs.removeObserver(obs, topic); 267 equal(data, JSON.stringify({ isLocalDevice: false })); 268 resolve(); 269 }, ON_DEVICE_DISCONNECTED_NOTIFICATION); 270 }); 271 272 let pushService = new FxAccountsPushService({ 273 pushService: mockPushService, 274 fxai: fxAccountsMock, 275 }); 276 277 pushService.observe( 278 msg, 279 mockPushService.pushTopic, 280 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 281 ); 282 283 await deviceDisconnectedNotificationObserved; 284 ok(!signoutCalled); 285 }); 286 287 add_test(function observePushTopicAccountDestroyed() { 288 const uid = "bogusuid"; 289 let msg = { 290 data: { 291 json: () => ({ 292 command: ON_ACCOUNT_DESTROYED_NOTIFICATION, 293 data: { 294 uid, 295 }, 296 }), 297 }, 298 QueryInterface() { 299 return this; 300 }, 301 }; 302 let customAccounts = Object.assign(mockFxAccounts, { 303 _handleAccountDestroyed() { 304 // checking verification status on push messages without data 305 run_next_test(); 306 }, 307 }); 308 309 let pushService = new FxAccountsPushService({ 310 pushService: mockPushService, 311 fxai: customAccounts, 312 }); 313 314 pushService.observe( 315 msg, 316 mockPushService.pushTopic, 317 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 318 ); 319 }); 320 321 add_test(function observePushTopicVerifyLogin() { 322 let url = "http://localhost/newLogin"; 323 let title = "bogustitle"; 324 let body = "bogusbody"; 325 let msg = { 326 data: { 327 json: () => ({ 328 command: ON_VERIFY_LOGIN_NOTIFICATION, 329 data: { 330 body, 331 title, 332 url, 333 }, 334 }), 335 }, 336 QueryInterface() { 337 return this; 338 }, 339 }; 340 let obs = (subject, topic, data) => { 341 Services.obs.removeObserver(obs, topic); 342 Assert.equal(data, JSON.stringify(msg.data.json().data)); 343 run_next_test(); 344 }; 345 Services.obs.addObserver(obs, ON_VERIFY_LOGIN_NOTIFICATION); 346 347 let pushService = new FxAccountsPushService({ 348 pushService: mockPushService, 349 fxai: mockFxAccounts, 350 }); 351 352 pushService.observe( 353 msg, 354 mockPushService.pushTopic, 355 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 356 ); 357 }); 358 359 add_test(function observePushTopicProfileUpdated() { 360 let msg = { 361 data: { 362 json: () => ({ 363 command: ON_PROFILE_UPDATED_NOTIFICATION, 364 }), 365 }, 366 QueryInterface() { 367 return this; 368 }, 369 }; 370 let obs = (subject, topic) => { 371 Services.obs.removeObserver(obs, topic); 372 run_next_test(); 373 }; 374 Services.obs.addObserver(obs, ON_PROFILE_CHANGE_NOTIFICATION); 375 376 let pushService = new FxAccountsPushService({ 377 pushService: mockPushService, 378 fxai: mockFxAccounts, 379 }); 380 381 pushService.observe( 382 msg, 383 mockPushService.pushTopic, 384 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 385 ); 386 }); 387 388 add_test(function observePushTopicPasswordChanged() { 389 let msg = { 390 data: { 391 json: () => ({ 392 command: ON_PASSWORD_CHANGED_NOTIFICATION, 393 }), 394 }, 395 QueryInterface() { 396 return this; 397 }, 398 }; 399 400 let pushService = new FxAccountsPushService({ 401 pushService: mockPushService, 402 }); 403 404 pushService._onPasswordChanged = function () { 405 run_next_test(); 406 }; 407 408 pushService.observe( 409 msg, 410 mockPushService.pushTopic, 411 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 412 ); 413 }); 414 415 add_test(function observePushTopicPasswordReset() { 416 let msg = { 417 data: { 418 json: () => ({ 419 command: ON_PASSWORD_RESET_NOTIFICATION, 420 }), 421 }, 422 QueryInterface() { 423 return this; 424 }, 425 }; 426 427 let pushService = new FxAccountsPushService({ 428 pushService: mockPushService, 429 }); 430 431 pushService._onPasswordChanged = function () { 432 run_next_test(); 433 }; 434 435 pushService.observe( 436 msg, 437 mockPushService.pushTopic, 438 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 439 ); 440 }); 441 442 add_task(async function commandReceived() { 443 let msg = { 444 data: { 445 json: () => ({ 446 command: "fxaccounts:command_received", 447 data: { 448 url: "https://api.accounts.firefox.com/auth/v1/account/device/commands?index=42&limit=1", 449 }, 450 }), 451 }, 452 QueryInterface() { 453 return this; 454 }, 455 }; 456 457 let fxAccountsMock = {}; 458 const promiseConsumeRemoteMessagesCalled = new Promise(res => { 459 fxAccountsMock.commands = { 460 pollDeviceCommands() { 461 res(); 462 }, 463 }; 464 }); 465 466 let pushService = new FxAccountsPushService({ 467 pushService: mockPushService, 468 fxai: fxAccountsMock, 469 }); 470 471 pushService.observe( 472 msg, 473 mockPushService.pushTopic, 474 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 475 ); 476 await promiseConsumeRemoteMessagesCalled; 477 }); 478 479 add_test(function observeSubscriptionChangeTopic() { 480 let customAccounts = Object.assign(mockFxAccounts, { 481 updateDeviceRegistration() { 482 // subscription change means updating the device registration 483 run_next_test(); 484 }, 485 }); 486 487 let pushService = new FxAccountsPushService({ 488 pushService: mockPushService, 489 fxai: customAccounts, 490 }); 491 492 pushService.observe( 493 null, 494 mockPushService.subscriptionChangeTopic, 495 FXA_PUSH_SCOPE_ACCOUNT_UPDATE 496 ); 497 });