test_broadcast_success.js (11685B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Create the profile directory early to ensure pushBroadcastService 7 // is initialized with the correct path 8 do_get_profile(); 9 const { BroadcastService } = ChromeUtils.importESModule( 10 "resource://gre/modules/PushBroadcastService.sys.mjs" 11 ); 12 const { JSONFile } = ChromeUtils.importESModule( 13 "resource://gre/modules/JSONFile.sys.mjs" 14 ); 15 16 const { FileTestUtils } = ChromeUtils.importESModule( 17 "resource://testing-common/FileTestUtils.sys.mjs" 18 ); 19 const { broadcastHandler } = ChromeUtils.importESModule( 20 "resource://test/broadcast_handler.sys.mjs" 21 ); 22 23 const broadcastService = pushBroadcastService; 24 const assert = Assert; 25 const userAgentID = "bd744428-f125-436a-b6d0-dd0c9845837f"; 26 const channelID = "0ef2ad4a-6c49-41ad-af6e-95d2425276bf"; 27 28 function run_test() { 29 setPrefs({ 30 userAgentID, 31 alwaysConnect: true, 32 requestTimeout: 1000, 33 retryBaseInterval: 150, 34 }); 35 run_next_test(); 36 } 37 38 function getPushServiceMock() { 39 return { 40 subscribed: [], 41 subscribeBroadcast(broadcastId, version) { 42 this.subscribed.push([broadcastId, version]); 43 }, 44 }; 45 } 46 47 add_task(async function test_register_success() { 48 await broadcastService._resetListeners(); 49 const db = PushServiceWebSocket.newPushDB(); 50 broadcastHandler.reset(); 51 const notifications = broadcastHandler.notifications; 52 let socket; 53 registerCleanupFunction(() => { 54 return db.drop().then(_ => db.close()); 55 }); 56 57 await broadcastService.addListener("broadcast-test", "2018-02-01", { 58 moduleURI: "resource://test/broadcast_handler.sys.mjs", 59 symbolName: "broadcastHandler", 60 }); 61 62 PushServiceWebSocket._generateID = () => channelID; 63 64 var broadcastSubscriptions = []; 65 66 let handshakeDone; 67 let handshakePromise = new Promise(resolve => (handshakeDone = resolve)); 68 await PushService.init({ 69 serverURI: "wss://push.example.org/", 70 db, 71 makeWebSocket(uri) { 72 return new MockWebSocket(uri, { 73 onHello(data) { 74 socket = this; 75 deepEqual( 76 data.broadcasts, 77 { "broadcast-test": "2018-02-01" }, 78 "Handshake: doesn't consult listeners" 79 ); 80 equal(data.messageType, "hello", "Handshake: wrong message type"); 81 ok( 82 !data.uaid, 83 "Should not send UAID in handshake without local subscriptions" 84 ); 85 this.serverSendMsg( 86 JSON.stringify({ 87 messageType: "hello", 88 status: 200, 89 uaid: userAgentID, 90 }) 91 ); 92 handshakeDone(); 93 }, 94 95 onBroadcastSubscribe(data) { 96 broadcastSubscriptions.push(data); 97 }, 98 }); 99 }, 100 }); 101 await handshakePromise; 102 103 socket.serverSendMsg( 104 JSON.stringify({ 105 messageType: "broadcast", 106 broadcasts: { 107 "broadcast-test": "2018-03-02", 108 }, 109 }) 110 ); 111 112 await broadcastHandler.wasNotified; 113 114 deepEqual( 115 notifications, 116 [ 117 [ 118 "2018-03-02", 119 "broadcast-test", 120 { phase: broadcastService.PHASES.BROADCAST }, 121 ], 122 ], 123 "Broadcast notification didn't get delivered" 124 ); 125 126 deepEqual( 127 await broadcastService.getListeners(), 128 { 129 "broadcast-test": "2018-03-02", 130 }, 131 "Broadcast version wasn't updated" 132 ); 133 134 await broadcastService.addListener("example-listener", "2018-03-01", { 135 // NOTE: This file is non-existing. 136 moduleURI: "resource://gre/modules/not-real-example.sys.mjs", 137 symbolName: "doesntExist", 138 }); 139 140 deepEqual(broadcastSubscriptions, [ 141 { 142 messageType: "broadcast_subscribe", 143 broadcasts: { "example-listener": "2018-03-01" }, 144 }, 145 ]); 146 }); 147 148 add_task(async function test_handle_hello_broadcasts() { 149 PushService.uninit(); 150 await broadcastService._resetListeners(); 151 let db = PushServiceWebSocket.newPushDB(); 152 broadcastHandler.reset(); 153 let notifications = broadcastHandler.notifications; 154 registerCleanupFunction(() => { 155 return db.drop().then(_ => db.close()); 156 }); 157 158 await broadcastService.addListener("broadcast-test", "2018-02-01", { 159 moduleURI: "resource://test/broadcast_handler.sys.mjs", 160 symbolName: "broadcastHandler", 161 }); 162 163 PushServiceWebSocket._generateID = () => channelID; 164 165 await PushService.init({ 166 serverURI: "wss://push.example.org/", 167 db, 168 makeWebSocket(uri) { 169 return new MockWebSocket(uri, { 170 onHello(data) { 171 deepEqual( 172 data.broadcasts, 173 { "broadcast-test": "2018-02-01" }, 174 "Handshake: doesn't consult listeners" 175 ); 176 equal(data.messageType, "hello", "Handshake: wrong message type"); 177 ok( 178 !data.uaid, 179 "Should not send UAID in handshake without local subscriptions" 180 ); 181 this.serverSendMsg( 182 JSON.stringify({ 183 messageType: "hello", 184 status: 200, 185 uaid: userAgentID, 186 broadcasts: { 187 "broadcast-test": "2018-02-02", 188 }, 189 }) 190 ); 191 }, 192 193 onBroadcastSubscribe() {}, 194 }); 195 }, 196 }); 197 198 await broadcastHandler.wasNotified; 199 200 deepEqual( 201 notifications, 202 [ 203 [ 204 "2018-02-02", 205 "broadcast-test", 206 { phase: broadcastService.PHASES.HELLO }, 207 ], 208 ], 209 "Broadcast notification on hello was delivered" 210 ); 211 212 deepEqual( 213 await broadcastService.getListeners(), 214 { 215 "broadcast-test": "2018-02-02", 216 }, 217 "Broadcast version wasn't updated" 218 ); 219 }); 220 221 add_task(async function test_broadcast_context() { 222 await broadcastService._resetListeners(); 223 const db = PushServiceWebSocket.newPushDB(); 224 broadcastHandler.reset(); 225 registerCleanupFunction(() => { 226 return db.drop().then(() => db.close()); 227 }); 228 229 const serviceId = "broadcast-test"; 230 const version = "2018-02-01"; 231 await broadcastService.addListener(serviceId, version, { 232 moduleURI: "resource://test/broadcast_handler.sys.mjs", 233 symbolName: "broadcastHandler", 234 }); 235 236 // PushServiceWebSocket._generateID = () => channelID; 237 238 await PushService.init({ 239 serverURI: "wss://push.example.org/", 240 db, 241 makeWebSocket(uri) { 242 return new MockWebSocket(uri, { 243 onHello() {}, 244 }); 245 }, 246 }); 247 248 // Simulate registration. 249 PushServiceWebSocket.sendSubscribeBroadcast(serviceId, version); 250 251 // Simulate broadcast reply received by PushWebSocketListener. 252 const message = JSON.stringify({ 253 messageType: "broadcast", 254 broadcasts: { 255 [serviceId]: version, 256 }, 257 }); 258 PushServiceWebSocket._wsOnMessageAvailable({}, message); 259 await broadcastHandler.wasNotified; 260 261 deepEqual( 262 broadcastHandler.notifications, 263 [[version, serviceId, { phase: broadcastService.PHASES.REGISTER }]], 264 "Broadcast passes REGISTER context" 265 ); 266 267 // Simulate broadcast reply, without previous registration. 268 broadcastHandler.reset(); 269 PushServiceWebSocket._wsOnMessageAvailable({}, message); 270 await broadcastHandler.wasNotified; 271 272 deepEqual( 273 broadcastHandler.notifications, 274 [[version, serviceId, { phase: broadcastService.PHASES.BROADCAST }]], 275 "Broadcast passes BROADCAST context" 276 ); 277 }); 278 279 add_task(async function test_broadcast_unit() { 280 const fakeListenersData = { 281 abc: { 282 version: "2018-03-04", 283 sourceInfo: { 284 // NOTE: This file is non-existing. 285 moduleURI: "resource://gre/modules/abc.sys.mjs", 286 symbolName: "getAbc", 287 }, 288 }, 289 def: { 290 version: "2018-04-05", 291 sourceInfo: { 292 // NOTE: This file is non-existing. 293 moduleURI: "resource://gre/modules/def.sys.mjs", 294 symbolName: "getDef", 295 }, 296 }, 297 }; 298 const path = FileTestUtils.getTempFile("broadcast-listeners.json").path; 299 300 const jsonFile = new JSONFile({ path }); 301 jsonFile.data = { 302 listeners: fakeListenersData, 303 }; 304 await jsonFile._save(); 305 306 const pushServiceMock = getPushServiceMock(); 307 308 const mockBroadcastService = new BroadcastService(pushServiceMock, path); 309 const listeners = await mockBroadcastService.getListeners(); 310 deepEqual(listeners, { 311 abc: "2018-03-04", 312 def: "2018-04-05", 313 }); 314 315 await mockBroadcastService.addListener("ghi", "2018-05-06", { 316 // NOTE: This file is non-existing. 317 moduleURI: "resource://gre/modules/ghi.sys.mjs", 318 symbolName: "getGhi", 319 }); 320 321 deepEqual(pushServiceMock.subscribed, [["ghi", "2018-05-06"]]); 322 323 await mockBroadcastService._saveImmediately(); 324 325 const newJSONFile = new JSONFile({ path }); 326 await newJSONFile.load(); 327 328 deepEqual(newJSONFile.data, { 329 listeners: { 330 ...fakeListenersData, 331 ghi: { 332 version: "2018-05-06", 333 sourceInfo: { 334 // NOTE: This file is non-existing. 335 moduleURI: "resource://gre/modules/ghi.sys.mjs", 336 symbolName: "getGhi", 337 }, 338 }, 339 }, 340 version: 1, 341 }); 342 343 deepEqual(await mockBroadcastService.getListeners(), { 344 abc: "2018-03-04", 345 def: "2018-04-05", 346 ghi: "2018-05-06", 347 }); 348 }); 349 350 add_task(async function test_broadcast_initialize_sane() { 351 const path = FileTestUtils.getTempFile("broadcast-listeners.json").path; 352 const mockBroadcastService = new BroadcastService(getPushServiceMock(), path); 353 deepEqual( 354 await mockBroadcastService.getListeners(), 355 {}, 356 "listeners should start out sane" 357 ); 358 await mockBroadcastService._saveImmediately(); 359 let onDiskJSONFile = new JSONFile({ path }); 360 await onDiskJSONFile.load(); 361 deepEqual( 362 onDiskJSONFile.data, 363 { listeners: {}, version: 1 }, 364 "written JSON file has listeners and version fields" 365 ); 366 367 await mockBroadcastService.addListener("ghi", "2018-05-06", { 368 // NOTE: This file is non-existing. 369 moduleURI: "resource://gre/modules/ghi.sys.mjs", 370 symbolName: "getGhi", 371 }); 372 373 await mockBroadcastService._saveImmediately(); 374 375 onDiskJSONFile = new JSONFile({ path }); 376 await onDiskJSONFile.load(); 377 378 deepEqual( 379 onDiskJSONFile.data, 380 { 381 listeners: { 382 ghi: { 383 version: "2018-05-06", 384 sourceInfo: { 385 // NOTE: This file is non-existing. 386 moduleURI: "resource://gre/modules/ghi.sys.mjs", 387 symbolName: "getGhi", 388 }, 389 }, 390 }, 391 version: 1, 392 }, 393 "adding listeners to initial state is written OK" 394 ); 395 }); 396 397 add_task(async function test_broadcast_reject_invalid_sourceinfo() { 398 const path = FileTestUtils.getTempFile("broadcast-listeners.json").path; 399 const mockBroadcastService = new BroadcastService(getPushServiceMock(), path); 400 401 await assert.rejects( 402 mockBroadcastService.addListener("ghi", "2018-05-06", { 403 // NOTE: This file is non-existing. 404 moduleName: "resource://gre/modules/ghi.sys.mjs", 405 symbolName: "getGhi", 406 }), 407 /moduleURI must be a string/, 408 "rejects sourceInfo that doesn't have moduleURI" 409 ); 410 }); 411 412 add_task(async function test_broadcast_reject_version_not_string() { 413 await assert.rejects( 414 broadcastService.addListener( 415 "ghi", 416 {}, 417 { 418 // NOTE: This file is non-existing. 419 moduleURI: "resource://gre/modules/ghi.sys.mjs", 420 symbolName: "getGhi", 421 } 422 ), 423 /version should be a string/, 424 "rejects version that isn't a string" 425 ); 426 }); 427 428 add_task(async function test_broadcast_reject_version_empty_string() { 429 await assert.rejects( 430 broadcastService.addListener("ghi", "", { 431 // NOTE: This file is non-existing. 432 moduleURI: "resource://gre/modules/ghi.sys.mjs", 433 symbolName: "getGhi", 434 }), 435 /version should not be an empty string/, 436 "rejects version that is an empty string" 437 ); 438 });