summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/test_PingSender.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/test_PingSender.js')
-rw-r--r--toolkit/components/telemetry/tests/unit/test_PingSender.js284
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();
+});