diff options
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/test_PingSender.js')
-rw-r--r-- | toolkit/components/telemetry/tests/unit/test_PingSender.js | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/unit/test_PingSender.js b/toolkit/components/telemetry/tests/unit/test_PingSender.js new file mode 100644 index 0000000000..7c9c920025 --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/test_PingSender.js @@ -0,0 +1,284 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +*/ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +// This tests submitting a ping using the stand-alone pingsender program. + +"use strict"; + +const { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.sys.mjs" +); +const { TelemetrySend } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetrySend.sys.mjs" +); +const { TelemetryStorage } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryStorage.sys.mjs" +); +const { TelemetryUtils } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryUtils.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +function generateTestPingData() { + return { + type: "test-pingsender-type", + id: TelemetryUtils.generateUUID(), + creationDate: new Date().toISOString(), + version: 4, + payload: { + dummy: "stuff", + }, + }; +} + +function testSendingPings(pingPaths) { + const url = "http://localhost:" + PingServer.port + "/submit/telemetry/"; + const pings = pingPaths.map(path => { + return { + url, + path, + }; + }); + TelemetrySend.testRunPingSender(pings, (_, topic, __) => { + switch (topic) { + case "process-finished": // finished indicates an exit code of 0 + Assert.ok(true, "Pingsender should be able to post to localhost"); + break; + case "process-failed": // failed indicates an exit code != 0 + Assert.ok(false, "Pingsender should be able to post to localhost"); + break; + } + }); +} + +/** + * Wait for a ping file to be deleted from the pending pings directory. + */ +function waitForPingDeletion(pingId) { + const path = PathUtils.join(TelemetryStorage.pingDirectoryPath, pingId); + + let checkFn = (resolve, reject) => + setTimeout(() => { + IOUtils.exists(path).then(exists => { + if (!exists) { + Assert.ok(true, `${pingId} was deleted`); + resolve(); + } else { + checkFn(resolve, reject); + } + }, reject); + }, 250); + + return new Promise((resolve, reject) => checkFn(resolve, reject)); +} + +add_task(async function setup() { + // Init the profile. + do_get_profile(true); + + Services.prefs.setBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, true); + + // Start the ping server and let Telemetry know about it. + PingServer.start(); +}); + +async function test_pingSender(version = "1.0") { + // Generate a new ping and save it among the pending pings. + const data = generateTestPingData(); + await TelemetryStorage.savePing(data, true); + + // Get the local path of the saved ping. + const pingPath = PathUtils.join(TelemetryStorage.pingDirectoryPath, data.id); + + // Spawn an HTTP server that returns an error. We will be running the + // PingSender twice, trying to send the ping to this server. After the + // second time, we will resolve |deferred404Hit|. + let failingServer = new HttpServer(); + let deferred404Hit = PromiseUtils.defer(); + let hitCount = 0; + failingServer.registerPathHandler("/lookup_fail", (metadata, response) => { + response.setStatusLine("1.1", 404, "Not Found"); + hitCount++; + + if (hitCount >= 2) { + // Resolve the promise on the next tick. + Services.tm.dispatchToMainThread(() => deferred404Hit.resolve()); + } + }); + failingServer.start(-1); + + // Try to send the ping twice using the pingsender (we expect 404 both times). + const errorUrl = + "http://localhost:" + failingServer.identity.primaryPort + "/lookup_fail"; + TelemetrySend.testRunPingSender([{ url: errorUrl, path: pingPath }]); + TelemetrySend.testRunPingSender([{ url: errorUrl, path: pingPath }]); + + // Wait until we hit the 404 server twice. After that, make sure that the ping + // still exists locally. + await deferred404Hit.promise; + Assert.ok( + await IOUtils.exists(pingPath), + "The pending ping must not be deleted if we fail to send using the PingSender" + ); + + // Try to send it using the pingsender. + testSendingPings([pingPath]); + + let req = await PingServer.promiseNextRequest(); + let ping = decodeRequestPayload(req); + + Assert.equal( + req.getHeader("User-Agent"), + `pingsender/${version}`, + "Should have received the correct user agent string." + ); + Assert.equal( + req.getHeader("X-PingSender-Version"), + version, + "Should have received the correct PingSender version string." + ); + Assert.equal( + req.getHeader("Content-Encoding"), + "gzip", + "Should have a gzip encoded ping." + ); + Assert.ok(req.getHeader("Date"), "Should have received a Date header."); + Assert.equal(ping.id, data.id, "Should have received the correct ping id."); + Assert.equal( + ping.type, + data.type, + "Should have received the correct ping type." + ); + Assert.deepEqual( + ping.payload, + data.payload, + "Should have received the correct payload." + ); + + // Check that the PingSender removed the pending ping. + await waitForPingDeletion(data.id); + + // Shut down the failing server. + await new Promise(r => failingServer.stop(r)); +} + +add_task(async function test_pingsender1() { + let orig = Services.prefs.getBoolPref( + "toolkit.telemetry.shutdownPingSender.backgroundtask.enabled", + false + ); + try { + Services.prefs.setBoolPref( + "toolkit.telemetry.shutdownPingSender.backgroundtask.enabled", + false + ); + await test_pingSender("1.0"); + } finally { + Services.prefs.setBoolPref( + "toolkit.telemetry.shutdownPingSender.backgroundtask.enabled", + orig + ); + } +}); + +add_task(async function test_pingsender2() { + let orig = Services.prefs.getBoolPref( + "toolkit.telemetry.shutdownPingSender.backgroundtask.enabled", + false + ); + try { + Services.prefs.setBoolPref( + "toolkit.telemetry.shutdownPingSender.backgroundtask.enabled", + true + ); + await test_pingSender("2.0"); + } finally { + Services.prefs.setBoolPref( + "toolkit.telemetry.shutdownPingSender.backgroundtask.enabled", + orig + ); + } +}); + +add_task(async function test_bannedDomains() { + // Generate a new ping and save it among the pending pings. + const data = generateTestPingData(); + await TelemetryStorage.savePing(data, true); + + // Get the local path of the saved ping. + const pingPath = PathUtils.join(TelemetryStorage.pingDirectoryPath, data.id); + + // Confirm we can't send a ping to another destination url + let bannedUris = [ + "https://example.com", + "http://localhost.com", + "http://localHOST.com", + "http://localhost@example.com", + "http://localhost:bob@example.com", + "http://localhost:localhost@localhost.example.com", + ]; + for (let url of bannedUris) { + let result = await new Promise(resolve => + TelemetrySend.testRunPingSender( + [{ url, path: pingPath }], + (_, topic, __) => { + switch (topic) { + case "process-finished": // finished indicates an exit code of 0 + case "process-failed": // failed indicates an exit code != 0 + resolve(topic); + } + } + ) + ); + Assert.equal( + result, + "process-failed", + `Pingsender should not be able to post to ${url}` + ); + } +}); + +add_task(async function test_pingSender_multiple_pings() { + // Generate two new pings and save them among the pending pings. + const data = [generateTestPingData(), generateTestPingData()]; + + for (const d of data) { + await TelemetryStorage.savePing(d, true); + } + + // Get the local path of the saved pings. + const pingPaths = data.map(d => + PathUtils.join(TelemetryStorage.pingDirectoryPath, d.id) + ); + + // Try to send them using the pingsender. + testSendingPings(pingPaths); + + // Check the pings. We don't have an ordering guarantee, so we move the + // elements to a new array when we find them. + let data2 = []; + while (data.length) { + let req = await PingServer.promiseNextRequest(); + let ping = decodeRequestPayload(req); + let idx = data.findIndex(d => d.id == ping.id); + Assert.ok( + idx >= 0, + `Should have received the correct ping id: ${data[idx].id}` + ); + data2.push(data[idx]); + data.splice(idx, 1); + } + + // Check that the PingSender removed the pending pings. + for (const d of data2) { + await waitForPingDeletion(d.id); + } +}); + +add_task(async function cleanup() { + await PingServer.stop(); +}); |