forked from mirrors/gecko-dev
		
	Differential Revision: https://phabricator.services.mozilla.com/D59705 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			229 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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";
 | 
						|
 | 
						|
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/TelemetrySend.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/TelemetryStorage.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/Timer.jsm", this);
 | 
						|
 | 
						|
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 = OS.Path.join(TelemetryStorage.pingDirectoryPath, pingId);
 | 
						|
 | 
						|
  let checkFn = (resolve, reject) =>
 | 
						|
    setTimeout(() => {
 | 
						|
      OS.File.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();
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_pingSender() {
 | 
						|
  // 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 = OS.Path.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 OS.File.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/1.0",
 | 
						|
    "Should have received the correct user agent string."
 | 
						|
  );
 | 
						|
  Assert.equal(
 | 
						|
    req.getHeader("X-PingSender-Version"),
 | 
						|
    "1.0",
 | 
						|
    "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);
 | 
						|
 | 
						|
  // 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 indx in bannedUris) {
 | 
						|
    TelemetrySend.testRunPingSender(
 | 
						|
      [{ url: bannedUris[indx], path: pingPath }],
 | 
						|
      (_, topic, __) => {
 | 
						|
        switch (topic) {
 | 
						|
          case "process-finished": // finished indicates an exit code of 0
 | 
						|
            Assert.equal(
 | 
						|
              false,
 | 
						|
              true,
 | 
						|
              "Pingsender should not be able to post to any banned urls: " +
 | 
						|
                bannedUris[indx]
 | 
						|
            );
 | 
						|
            break;
 | 
						|
          case "process-failed": // failed indicates an exit code != 0
 | 
						|
            Assert.equal(
 | 
						|
              true,
 | 
						|
              true,
 | 
						|
              "Pingsender should not be able to post to any banned urls: " +
 | 
						|
                bannedUris[indx]
 | 
						|
            );
 | 
						|
            break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  // Shut down the failing server. We do this now, and not right after using it,
 | 
						|
  // to make sure we're not interfering with the test.
 | 
						|
  await new Promise(r => failingServer.stop(r));
 | 
						|
});
 | 
						|
 | 
						|
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 =>
 | 
						|
    OS.Path.join(TelemetryStorage.pingDirectoryPath, d.id)
 | 
						|
  );
 | 
						|
 | 
						|
  // Try to send them using the pingsender.
 | 
						|
  testSendingPings(pingPaths);
 | 
						|
 | 
						|
  // Check the pings
 | 
						|
  for (const d of data) {
 | 
						|
    let req = await PingServer.promiseNextRequest();
 | 
						|
    let ping = decodeRequestPayload(req);
 | 
						|
    Assert.equal(ping.id, d.id, "Should have received the correct ping id.");
 | 
						|
  }
 | 
						|
 | 
						|
  // Check that the PingSender removed the pending pings.
 | 
						|
  for (const d of data) {
 | 
						|
    await waitForPingDeletion(d.id);
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function cleanup() {
 | 
						|
  await PingServer.stop();
 | 
						|
});
 |