summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/test/unit/lib/TelemetryFeed.test.js')
-rw-r--r--browser/components/newtab/test/unit/lib/TelemetryFeed.test.js2606
1 files changed, 2606 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js b/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
new file mode 100644
index 0000000000..1606f98e94
--- /dev/null
+++ b/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
@@ -0,0 +1,2606 @@
+/* global Services */
+import {
+ actionCreators as ac,
+ actionTypes as at,
+ actionUtils as au,
+} from "common/Actions.sys.mjs";
+import {
+ ASRouterEventPing,
+ BasePing,
+ ImpressionStatsPing,
+ SessionPing,
+ UserEventPing,
+} from "test/schemas/pings";
+import { FAKE_GLOBAL_PREFS, GlobalOverrider } from "test/unit/utils";
+import { ASRouterPreferences } from "lib/ASRouterPreferences.jsm";
+import injector from "inject!lib/TelemetryFeed.jsm";
+import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.sys.mjs";
+
+const FAKE_UUID = "{foo-123-foo}";
+const FAKE_ROUTER_MESSAGE_PROVIDER = [{ id: "cfr", enabled: true }];
+const FAKE_TELEMETRY_ID = "foo123";
+
+// eslint-disable-next-line max-statements
+describe("TelemetryFeed", () => {
+ let globals;
+ let sandbox;
+ let expectedUserPrefs;
+ let browser = {
+ getAttribute() {
+ return "true";
+ },
+ };
+ let instance;
+ let clock;
+ let fakeHomePageUrl;
+ let fakeHomePage;
+ let fakeExtensionSettingsStore;
+ let ExperimentAPI = { getExperimentMetaData: () => {} };
+ class PingCentre {
+ sendPing() {}
+ uninit() {}
+ sendStructuredIngestionPing() {}
+ }
+ class UTEventReporting {
+ sendUserEvent() {}
+ sendSessionEndEvent() {}
+ uninit() {}
+ }
+
+ // Reset the global prefs before importing the `TelemetryFeed` module, to
+ // avoid a coverage miss caused by preference pollution when this test and
+ // `ActivityStream.test.js` are run together.
+ //
+ // The `TelemetryFeed` module defines a lazy `contextId` getter, which the
+ // `XPCOMUtils.defineLazyGetter` mock (defined in `unit-entry.js`) executes
+ // immediately, as soon as the module is imported.
+ //
+ // If this test runs first, there's no coverage miss: this test will load
+ // the `TelemetryFeed` module and run the lazy `contextId` getter, which will
+ // generate a fake context ID and store it in `FAKE_GLOBAL_PREFS`, covering
+ // all branches in the module. When `ActivityStream.test.js` runs, it'll load
+ // `TelemetryFeed` and run the lazy getter a second time, which will use the
+ // existing fake context ID from `FAKE_GLOBAL_PREFS` instead of generating a
+ // new one.
+ //
+ // But, if `ActivityStream.test.js` runs first, then loading `TelemetryFeed` a
+ // second time as part of this test will use the existing fake context ID from
+ // `FAKE_GLOBAL_PREFS`, missing coverage for the branch to generate a new
+ // context ID.
+ FAKE_GLOBAL_PREFS.clear();
+
+ const {
+ TelemetryFeed,
+ USER_PREFS_ENCODING,
+ PREF_IMPRESSION_ID,
+ TELEMETRY_PREF,
+ EVENTS_TELEMETRY_PREF,
+ STRUCTURED_INGESTION_ENDPOINT_PREF,
+ } = injector({
+ "lib/UTEventReporting.sys.mjs": { UTEventReporting },
+ });
+
+ beforeEach(() => {
+ globals = new GlobalOverrider();
+ sandbox = globals.sandbox;
+ clock = sinon.useFakeTimers();
+ fakeHomePageUrl = "about:home";
+ fakeHomePage = {
+ get() {
+ return fakeHomePageUrl;
+ },
+ };
+ fakeExtensionSettingsStore = {
+ initialize() {
+ return Promise.resolve();
+ },
+ getSetting() {},
+ };
+ sandbox.spy(global.console, "error");
+ globals.set("AboutNewTab", {
+ newTabURLOverridden: false,
+ newTabURL: "",
+ });
+ globals.set("pktApi", {
+ isUserLoggedIn: () => true,
+ });
+ globals.set("HomePage", fakeHomePage);
+ globals.set("ExtensionSettingsStore", fakeExtensionSettingsStore);
+ globals.set("PingCentre", PingCentre);
+ globals.set("UTEventReporting", UTEventReporting);
+ globals.set("ClientID", {
+ getClientID: sandbox.spy(async () => FAKE_TELEMETRY_ID),
+ });
+ globals.set("ExperimentAPI", ExperimentAPI);
+
+ sandbox
+ .stub(ASRouterPreferences, "providers")
+ .get(() => FAKE_ROUTER_MESSAGE_PROVIDER);
+ instance = new TelemetryFeed();
+ });
+ afterEach(() => {
+ clock.restore();
+ globals.restore();
+ FAKE_GLOBAL_PREFS.clear();
+ ASRouterPreferences.uninit();
+ });
+ describe("#init", () => {
+ it("should create an instance", () => {
+ const testInstance = new TelemetryFeed();
+ assert.isDefined(testInstance);
+ });
+ it("should add .pingCentre, a PingCentre instance", () => {
+ assert.instanceOf(instance.pingCentre, PingCentre);
+ });
+ it("should add .utEvents, a UTEventReporting instance", () => {
+ assert.instanceOf(instance.utEvents, UTEventReporting);
+ });
+ it("should make this.browserOpenNewtabStart() observe browser-open-newtab-start", () => {
+ sandbox.spy(Services.obs, "addObserver");
+
+ instance.init();
+
+ assert.calledTwice(Services.obs.addObserver);
+ assert.calledWithExactly(
+ Services.obs.addObserver,
+ instance.browserOpenNewtabStart,
+ "browser-open-newtab-start"
+ );
+ });
+ it("should add window open listener", () => {
+ sandbox.spy(Services.obs, "addObserver");
+
+ instance.init();
+
+ assert.calledTwice(Services.obs.addObserver);
+ assert.calledWithExactly(
+ Services.obs.addObserver,
+ instance._addWindowListeners,
+ "domwindowopened"
+ );
+ });
+ it("should add TabPinned event listener on new windows", () => {
+ const stub = { addEventListener: sandbox.stub() };
+ sandbox.spy(Services.obs, "addObserver");
+
+ instance.init();
+
+ assert.calledTwice(Services.obs.addObserver);
+ const [cb] = Services.obs.addObserver.secondCall.args;
+ cb(stub);
+ assert.calledTwice(stub.addEventListener);
+ assert.calledWithExactly(
+ stub.addEventListener,
+ "unload",
+ instance.handleEvent
+ );
+ assert.calledWithExactly(
+ stub.addEventListener,
+ "TabPinned",
+ instance.handleEvent
+ );
+ });
+ it("should create impression id if none exists", () => {
+ assert.equal(instance._impressionId, FAKE_UUID);
+ });
+ it("should set impression id if it exists", () => {
+ FAKE_GLOBAL_PREFS.set(PREF_IMPRESSION_ID, "fakeImpressionId");
+ assert.equal(new TelemetryFeed()._impressionId, "fakeImpressionId");
+ });
+ it("should register listeners on existing windows", () => {
+ const stub = sandbox.stub();
+ globals.set({
+ Services: {
+ ...Services,
+ wm: { getEnumerator: () => [{ addEventListener: stub }] },
+ },
+ });
+
+ instance.init();
+
+ assert.calledTwice(stub);
+ assert.calledWithExactly(stub, "unload", instance.handleEvent);
+ assert.calledWithExactly(stub, "TabPinned", instance.handleEvent);
+ });
+ describe("telemetry pref changes from false to true", () => {
+ beforeEach(() => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, false);
+ instance = new TelemetryFeed();
+
+ assert.propertyVal(instance, "telemetryEnabled", false);
+ });
+
+ it("should set the enabled property to true", () => {
+ instance._prefs.set(TELEMETRY_PREF, true);
+
+ assert.propertyVal(instance, "telemetryEnabled", true);
+ });
+ });
+ describe("events telemetry pref changes from false to true", () => {
+ beforeEach(() => {
+ FAKE_GLOBAL_PREFS.set(EVENTS_TELEMETRY_PREF, false);
+ instance = new TelemetryFeed();
+
+ assert.propertyVal(instance, "eventTelemetryEnabled", false);
+ });
+
+ it("should set the enabled property to true", () => {
+ instance._prefs.set(EVENTS_TELEMETRY_PREF, true);
+
+ assert.propertyVal(instance, "eventTelemetryEnabled", true);
+ });
+ });
+ it("should set two scalars for deletion-request", () => {
+ sandbox.spy(Services.telemetry, "scalarSet");
+
+ instance.init();
+
+ assert.calledTwice(Services.telemetry.scalarSet);
+
+ // impression_id
+ let [type, value] = Services.telemetry.scalarSet.firstCall.args;
+ assert.equal(type, "deletion.request.impression_id");
+ assert.equal(value, instance._impressionId);
+
+ // context_id
+ [type, value] = Services.telemetry.scalarSet.secondCall.args;
+ assert.equal(type, "deletion.request.context_id");
+ assert.equal(value, FAKE_UUID);
+ });
+ describe("#_beginObservingNewtabPingPrefs", () => {
+ it("should record initial metrics from newtab prefs", () => {
+ FAKE_GLOBAL_PREFS.set(
+ "browser.newtabpage.activity-stream.feeds.topsites",
+ true
+ );
+ FAKE_GLOBAL_PREFS.set(
+ "browser.newtabpage.activity-stream.topSitesRows",
+ 3
+ );
+ FAKE_GLOBAL_PREFS.set(
+ "browser.topsites.blockedSponsors",
+ '["mozilla"]'
+ );
+
+ sandbox.spy(Glean.topsites.enabled, "set");
+ sandbox.spy(Glean.topsites.rows, "set");
+ sandbox.spy(Glean.newtab.blockedSponsors, "set");
+
+ instance = new TelemetryFeed();
+ instance.init();
+
+ assert.calledOnce(Glean.topsites.enabled.set);
+ assert.calledWith(Glean.topsites.enabled.set, true);
+ assert.calledOnce(Glean.topsites.rows.set);
+ assert.calledWith(Glean.topsites.rows.set, 3);
+ assert.calledOnce(Glean.newtab.blockedSponsors.set);
+ assert.calledWith(Glean.newtab.blockedSponsors.set, ["mozilla"]);
+ });
+
+ it("should not record blocked sponsor metrics when bad json string is passed", () => {
+ FAKE_GLOBAL_PREFS.set("browser.topsites.blockedSponsors", "BAD[JSON]");
+
+ sandbox.spy(Glean.newtab.blockedSponsors, "set");
+
+ instance = new TelemetryFeed();
+ instance.init();
+
+ assert.notCalled(Glean.newtab.blockedSponsors.set);
+ });
+
+ it("should record new metrics for newtab pref changes", () => {
+ FAKE_GLOBAL_PREFS.set(
+ "browser.newtabpage.activity-stream.topSitesRows",
+ 3
+ );
+ FAKE_GLOBAL_PREFS.set("browser.topsites.blockedSponsors", "[]");
+ sandbox.spy(Glean.topsites.rows, "set");
+ sandbox.spy(Glean.newtab.blockedSponsors, "set");
+
+ instance = new TelemetryFeed();
+ instance.init();
+
+ Services.prefs.setIntPref(
+ "browser.newtabpage.activity-stream.topSitesRows",
+ 2
+ );
+
+ Services.prefs.setStringPref(
+ "browser.topsites.blockedSponsors",
+ '["mozilla"]'
+ );
+
+ assert.calledTwice(Glean.topsites.rows.set);
+ assert.calledWith(Glean.topsites.rows.set.firstCall, 3);
+ assert.calledWith(Glean.topsites.rows.set.secondCall, 2);
+ assert.calledWith(Glean.newtab.blockedSponsors.set.firstCall, []);
+ assert.calledWith(Glean.newtab.blockedSponsors.set.secondCall, [
+ "mozilla",
+ ]);
+ });
+ it("should ignore changes to other prefs", () => {
+ FAKE_GLOBAL_PREFS.set("some.other.pref", 123);
+ FAKE_GLOBAL_PREFS.set(
+ "browser.newtabpage.activity-stream.impressionId",
+ "{foo-123-foo}"
+ );
+
+ instance = new TelemetryFeed();
+ instance.init();
+
+ Services.prefs.setIntPref("some.other.pref", 456);
+ Services.prefs.setCharPref(
+ "browser.newtabpage.activity-stream.impressionId",
+ "{foo-456-foo}"
+ );
+ });
+ });
+ });
+ describe("#handleEvent", () => {
+ it("should dispatch a TAB_PINNED_EVENT", () => {
+ sandbox.stub(instance, "sendEvent");
+ globals.set({
+ Services: {
+ ...Services,
+ wm: {
+ getEnumerator: () => [{ gBrowser: { tabs: [{ pinned: true }] } }],
+ },
+ },
+ });
+
+ instance.handleEvent({ type: "TabPinned", target: {} });
+
+ assert.calledOnce(instance.sendEvent);
+ const [ping] = instance.sendEvent.firstCall.args;
+ assert.propertyVal(ping, "event", "TABPINNED");
+ assert.propertyVal(ping, "source", "TAB_CONTEXT_MENU");
+ assert.propertyVal(ping, "session_id", "n/a");
+ assert.propertyVal(ping.value, "total_pinned_tabs", 1);
+ });
+ it("should skip private windows", () => {
+ sandbox.stub(instance, "sendEvent");
+ globals.set({ PrivateBrowsingUtils: { isWindowPrivate: () => true } });
+
+ instance.handleEvent({ type: "TabPinned", target: {} });
+
+ assert.notCalled(instance.sendEvent);
+ });
+ it("should return the correct value for total_pinned_tabs", () => {
+ sandbox.stub(instance, "sendEvent");
+ globals.set({
+ Services: {
+ ...Services,
+ wm: {
+ getEnumerator: () => [
+ {
+ gBrowser: { tabs: [{ pinned: true }, { pinned: false }] },
+ },
+ ],
+ },
+ },
+ });
+
+ instance.handleEvent({ type: "TabPinned", target: {} });
+
+ assert.calledOnce(instance.sendEvent);
+ const [ping] = instance.sendEvent.firstCall.args;
+ assert.propertyVal(ping, "event", "TABPINNED");
+ assert.propertyVal(ping, "source", "TAB_CONTEXT_MENU");
+ assert.propertyVal(ping, "session_id", "n/a");
+ assert.propertyVal(ping.value, "total_pinned_tabs", 1);
+ });
+ it("should return the correct value for total_pinned_tabs (when private windows are open)", () => {
+ sandbox.stub(instance, "sendEvent");
+ const privateWinStub = sandbox
+ .stub()
+ .onCall(0)
+ .returns(false)
+ .onCall(1)
+ .returns(true);
+ globals.set({
+ PrivateBrowsingUtils: { isWindowPrivate: privateWinStub },
+ });
+ globals.set({
+ Services: {
+ ...Services,
+ wm: {
+ getEnumerator: () => [
+ {
+ gBrowser: { tabs: [{ pinned: true }, { pinned: true }] },
+ },
+ ],
+ },
+ },
+ });
+
+ instance.handleEvent({ type: "TabPinned", target: {} });
+
+ assert.calledOnce(instance.sendEvent);
+ const [ping] = instance.sendEvent.firstCall.args;
+ assert.propertyVal(ping.value, "total_pinned_tabs", 0);
+ });
+ it("should unregister the event listeners", () => {
+ const stub = { removeEventListener: sandbox.stub() };
+
+ instance.handleEvent({ type: "unload", target: stub });
+
+ assert.calledTwice(stub.removeEventListener);
+ assert.calledWithExactly(
+ stub.removeEventListener,
+ "unload",
+ instance.handleEvent
+ );
+ assert.calledWithExactly(
+ stub.removeEventListener,
+ "TabPinned",
+ instance.handleEvent
+ );
+ });
+ });
+ describe("#addSession", () => {
+ it("should add a session and return it", () => {
+ const session = instance.addSession("foo");
+
+ assert.equal(instance.sessions.get("foo"), session);
+ });
+ it("should set the session_id", () => {
+ sandbox.spy(Services.uuid, "generateUUID");
+
+ const session = instance.addSession("foo");
+
+ assert.calledOnce(Services.uuid.generateUUID);
+ assert.equal(
+ session.session_id,
+ Services.uuid.generateUUID.firstCall.returnValue
+ );
+ });
+ it("should set the page if a url parameter is given", () => {
+ const session = instance.addSession("foo", "about:monkeys");
+
+ assert.propertyVal(session, "page", "about:monkeys");
+ });
+ it("should set the page prop to 'unknown' if no URL parameter given", () => {
+ const session = instance.addSession("foo");
+
+ assert.propertyVal(session, "page", "unknown");
+ });
+ it("should set the perf type when lacking timestamp", () => {
+ const session = instance.addSession("foo");
+
+ assert.propertyVal(session.perf, "load_trigger_type", "unexpected");
+ });
+ it("should set load_trigger_type to first_window_opened on the first about:home seen", () => {
+ const session = instance.addSession("foo", "about:home");
+
+ assert.propertyVal(
+ session.perf,
+ "load_trigger_type",
+ "first_window_opened"
+ );
+ });
+ it("should not set load_trigger_type to first_window_opened on the second about:home seen", () => {
+ instance.addSession("foo", "about:home");
+
+ const session2 = instance.addSession("foo", "about:home");
+
+ assert.notPropertyVal(
+ session2.perf,
+ "load_trigger_type",
+ "first_window_opened"
+ );
+ });
+ it("should set load_trigger_ts to the value of the process start timestamp", () => {
+ const session = instance.addSession("foo", "about:home");
+
+ assert.propertyVal(session.perf, "load_trigger_ts", 1588010448000);
+ });
+ it("should create a valid session ping on the first about:home seen", () => {
+ // Add a session
+ const portID = "foo";
+ const session = instance.addSession(portID, "about:home");
+
+ // Create a ping referencing the session
+ const ping = instance.createSessionEndEvent(session);
+ assert.validate(ping, SessionPing);
+ });
+ it("should be a valid ping with the data_late_by_ms perf", () => {
+ // Add a session
+ const portID = "foo";
+ const session = instance.addSession(portID, "about:home");
+ instance.saveSessionPerfData("foo", { topsites_data_late_by_ms: 10 });
+ instance.saveSessionPerfData("foo", { highlights_data_late_by_ms: 20 });
+
+ // Create a ping referencing the session
+ const ping = instance.createSessionEndEvent(session);
+ assert.validate(ping, SessionPing);
+ assert.propertyVal(
+ instance.sessions.get("foo").perf,
+ "highlights_data_late_by_ms",
+ 20
+ );
+ assert.propertyVal(
+ instance.sessions.get("foo").perf,
+ "topsites_data_late_by_ms",
+ 10
+ );
+ });
+ it("should be a valid ping with the topsites stats perf", () => {
+ // Add a session
+ const portID = "foo";
+ const session = instance.addSession(portID, "about:home");
+ instance.saveSessionPerfData("foo", {
+ topsites_icon_stats: {
+ custom_screenshot: 0,
+ screenshot_with_icon: 2,
+ screenshot: 1,
+ tippytop: 2,
+ rich_icon: 1,
+ no_image: 0,
+ },
+ topsites_pinned: 3,
+ topsites_search_shortcuts: 2,
+ });
+
+ // Create a ping referencing the session
+ const ping = instance.createSessionEndEvent(session);
+ assert.validate(ping, SessionPing);
+ assert.propertyVal(
+ instance.sessions.get("foo").perf.topsites_icon_stats,
+ "screenshot_with_icon",
+ 2
+ );
+ assert.equal(instance.sessions.get("foo").perf.topsites_pinned, 3);
+ assert.equal(
+ instance.sessions.get("foo").perf.topsites_search_shortcuts,
+ 2
+ );
+ });
+ });
+
+ describe("#browserOpenNewtabStart", () => {
+ it("should call ChromeUtils.addProfilerMarker with browser-open-newtab-start", () => {
+ globals.set("ChromeUtils", {
+ addProfilerMarker: sandbox.stub(),
+ });
+
+ sandbox.stub(global.Cu, "now").returns(12345);
+
+ instance.browserOpenNewtabStart();
+
+ assert.calledOnce(ChromeUtils.addProfilerMarker);
+ assert.calledWithExactly(
+ ChromeUtils.addProfilerMarker,
+ "UserTiming",
+ 12345,
+ "browser-open-newtab-start"
+ );
+ });
+ });
+
+ describe("#endSession", () => {
+ it("should not throw if there is no session for the given port ID", () => {
+ assert.doesNotThrow(() => instance.endSession("doesn't exist"));
+ });
+ it("should add a session_duration integer if there is a visibility_event_rcvd_ts", () => {
+ sandbox.stub(instance, "sendEvent");
+ const session = instance.addSession("foo");
+ session.perf.visibility_event_rcvd_ts = 444.4732;
+
+ instance.endSession("foo");
+
+ assert.isNumber(session.session_duration);
+ assert.ok(
+ Number.isInteger(session.session_duration),
+ "session_duration should be an integer"
+ );
+ });
+ it("shouldn't send session ping if there's no visibility_event_rcvd_ts", () => {
+ sandbox.stub(instance, "sendEvent");
+ instance.addSession("foo");
+
+ instance.endSession("foo");
+
+ assert.notCalled(instance.sendEvent);
+ assert.isFalse(instance.sessions.has("foo"));
+ });
+ it("should remove the session from .sessions", () => {
+ sandbox.stub(instance, "sendEvent");
+ instance.addSession("foo");
+
+ instance.endSession("foo");
+
+ assert.isFalse(instance.sessions.has("foo"));
+ });
+ it("should call createSessionSendEvent and sendEvent with the sesssion", () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ FAKE_GLOBAL_PREFS.set(EVENTS_TELEMETRY_PREF, true);
+ instance = new TelemetryFeed();
+
+ sandbox.stub(instance, "sendEvent");
+ sandbox.stub(instance, "createSessionEndEvent");
+ sandbox.stub(instance.utEvents, "sendSessionEndEvent");
+ const session = instance.addSession("foo");
+ session.perf.visibility_event_rcvd_ts = 444.4732;
+
+ instance.endSession("foo");
+
+ // Did we call sendEvent with the result of createSessionEndEvent?
+ assert.calledWith(instance.createSessionEndEvent, session);
+
+ let sessionEndEvent =
+ instance.createSessionEndEvent.firstCall.returnValue;
+ assert.calledWith(instance.sendEvent, sessionEndEvent);
+ assert.calledWith(instance.utEvents.sendSessionEndEvent, sessionEndEvent);
+ });
+ });
+ describe("ping creators", () => {
+ beforeEach(() => {
+ for (const pref of Object.keys(USER_PREFS_ENCODING)) {
+ FAKE_GLOBAL_PREFS.set(pref, true);
+ expectedUserPrefs |= USER_PREFS_ENCODING[pref];
+ }
+ instance.init();
+ });
+ describe("#createPing", () => {
+ it("should create a valid base ping without a session if no portID is supplied", async () => {
+ const ping = await instance.createPing();
+ assert.validate(ping, BasePing);
+ assert.notProperty(ping, "session_id");
+ assert.notProperty(ping, "page");
+ });
+ it("should create a valid base ping with session info if a portID is supplied", async () => {
+ // Add a session
+ const portID = "foo";
+ instance.addSession(portID, "about:home");
+ const sessionID = instance.sessions.get(portID).session_id;
+
+ // Create a ping referencing the session
+ const ping = await instance.createPing(portID);
+ assert.validate(ping, BasePing);
+
+ // Make sure we added the right session-related stuff to the ping
+ assert.propertyVal(ping, "session_id", sessionID);
+ assert.propertyVal(ping, "page", "about:home");
+ });
+ it("should create an unexpected base ping if no session yet portID is supplied", async () => {
+ const ping = await instance.createPing("foo");
+
+ assert.validate(ping, BasePing);
+ assert.propertyVal(ping, "page", "unknown");
+ assert.propertyVal(
+ instance.sessions.get("foo").perf,
+ "load_trigger_type",
+ "unexpected"
+ );
+ });
+ it("should create a base ping with user_prefs", async () => {
+ const ping = await instance.createPing("foo");
+
+ assert.validate(ping, BasePing);
+ assert.propertyVal(ping, "user_prefs", expectedUserPrefs);
+ });
+ });
+ describe("#createUserEvent", () => {
+ it("should create a valid event", async () => {
+ const portID = "foo";
+ const data = { source: "TOP_SITES", event: "CLICK" };
+ const action = ac.AlsoToMain(ac.UserEvent(data), portID);
+ const session = instance.addSession(portID);
+
+ const ping = await instance.createUserEvent(action);
+
+ // Is it valid?
+ assert.validate(ping, UserEventPing);
+ // Does it have the right session_id?
+ assert.propertyVal(ping, "session_id", session.session_id);
+ });
+ });
+ describe("#createSessionEndEvent", () => {
+ it("should create a valid event", async () => {
+ const ping = await instance.createSessionEndEvent({
+ session_id: FAKE_UUID,
+ page: "about:newtab",
+ session_duration: 12345,
+ perf: {
+ load_trigger_ts: 10,
+ load_trigger_type: "menu_plus_or_keyboard",
+ visibility_event_rcvd_ts: 20,
+ is_preloaded: true,
+ },
+ });
+
+ // Is it valid?
+ assert.validate(ping, SessionPing);
+ assert.propertyVal(ping, "session_id", FAKE_UUID);
+ assert.propertyVal(ping, "page", "about:newtab");
+ assert.propertyVal(ping, "session_duration", 12345);
+ });
+ it("should create a valid unexpected session event", async () => {
+ const ping = await instance.createSessionEndEvent({
+ session_id: FAKE_UUID,
+ page: "about:newtab",
+ session_duration: 12345,
+ perf: {
+ load_trigger_type: "unexpected",
+ is_preloaded: true,
+ },
+ });
+
+ // Is it valid?
+ assert.validate(ping, SessionPing);
+ assert.propertyVal(ping, "session_id", FAKE_UUID);
+ assert.propertyVal(ping, "page", "about:newtab");
+ assert.propertyVal(ping, "session_duration", 12345);
+ assert.propertyVal(ping.perf, "load_trigger_type", "unexpected");
+ });
+ });
+ });
+ describe("#createImpressionStats", () => {
+ it("should create a valid impression stats ping", async () => {
+ const tiles = [{ id: 10001 }, { id: 10002 }, { id: 10003 }];
+ const action = ac.ImpressionStats({ source: "POCKET", tiles });
+ const ping = await instance.createImpressionStats(
+ au.getPortIdOfSender(action),
+ action.data
+ );
+
+ assert.validate(ping, ImpressionStatsPing);
+ assert.propertyVal(ping, "source", "POCKET");
+ assert.propertyVal(ping, "tiles", tiles);
+ });
+ it("should create a valid click ping", async () => {
+ const tiles = [{ id: 10001, pos: 2 }];
+ const action = ac.ImpressionStats({ source: "POCKET", tiles, click: 0 });
+ const ping = await instance.createImpressionStats(
+ au.getPortIdOfSender(action),
+ action.data
+ );
+
+ assert.validate(ping, ImpressionStatsPing);
+ assert.propertyVal(ping, "click", 0);
+ assert.propertyVal(ping, "tiles", tiles);
+ });
+ it("should create a valid block ping", async () => {
+ const tiles = [{ id: 10001, pos: 2 }];
+ const action = ac.ImpressionStats({ source: "POCKET", tiles, block: 0 });
+ const ping = await instance.createImpressionStats(
+ au.getPortIdOfSender(action),
+ action.data
+ );
+
+ assert.validate(ping, ImpressionStatsPing);
+ assert.propertyVal(ping, "block", 0);
+ assert.propertyVal(ping, "tiles", tiles);
+ });
+ it("should create a valid pocket ping", async () => {
+ const tiles = [{ id: 10001, pos: 2 }];
+ const action = ac.ImpressionStats({ source: "POCKET", tiles, pocket: 0 });
+ const ping = await instance.createImpressionStats(
+ au.getPortIdOfSender(action),
+ action.data
+ );
+
+ assert.validate(ping, ImpressionStatsPing);
+ assert.propertyVal(ping, "pocket", 0);
+ assert.propertyVal(ping, "tiles", tiles);
+ });
+ it("should pass shim if it is available to impression ping", async () => {
+ const tiles = [{ id: 10001, pos: 2, shim: 1234 }];
+ const action = ac.ImpressionStats({ source: "POCKET", tiles });
+ const ping = await instance.createImpressionStats(
+ au.getPortIdOfSender(action),
+ action.data
+ );
+
+ assert.propertyVal(ping, "tiles", tiles);
+ assert.propertyVal(ping.tiles[0], "shim", tiles[0].shim);
+ });
+ it("should not include client_id and session_id", async () => {
+ const tiles = [{ id: 10001 }, { id: 10002 }, { id: 10003 }];
+ const action = ac.ImpressionStats({ source: "POCKET", tiles });
+ const ping = await instance.createImpressionStats(
+ au.getPortIdOfSender(action),
+ action.data
+ );
+
+ assert.validate(ping, ImpressionStatsPing);
+ assert.notProperty(ping, "client_id");
+ assert.notProperty(ping, "session_id");
+ });
+ });
+ describe("#applyCFRPolicy", () => {
+ it("should use client_id and message_id in prerelease", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "nightly";
+ },
+ });
+ const data = {
+ action: "cfr_user_event",
+ event: "IMPRESSION",
+ message_id: "cfr_message_01",
+ bucket_id: "cfr_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyCFRPolicy(data);
+
+ assert.equal(pingType, "cfr");
+ assert.isUndefined(ping.impression_id);
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
+ assert.propertyVal(ping, "message_id", "cfr_message_01");
+ });
+ it("should use impression_id and bucket_id in release", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "release";
+ },
+ });
+ const data = {
+ action: "cfr_user_event",
+ event: "IMPRESSION",
+ message_id: "cfr_message_01",
+ bucket_id: "cfr_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyCFRPolicy(data);
+
+ assert.equal(pingType, "cfr");
+ assert.isUndefined(ping.client_id);
+ assert.propertyVal(ping, "impression_id", FAKE_UUID);
+ assert.propertyVal(ping, "message_id", "n/a");
+ assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
+ });
+ it("should use client_id and message_id in the experiment cohort in release", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "release";
+ },
+ });
+ sandbox.stub(ExperimentAPI, "getExperimentMetaData").returns({
+ slug: "SOME-CFR-EXP",
+ });
+ const data = {
+ action: "cfr_user_event",
+ event: "IMPRESSION",
+ message_id: "cfr_message_01",
+ bucket_id: "cfr_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyCFRPolicy(data);
+
+ assert.equal(pingType, "cfr");
+ assert.isUndefined(ping.impression_id);
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
+ assert.propertyVal(ping, "message_id", "cfr_message_01");
+ });
+ it("should use impression_id and bucket_id in Private Browsing", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "release";
+ },
+ });
+ const data = {
+ action: "cfr_user_event",
+ event: "IMPRESSION",
+ is_private: true,
+ message_id: "cfr_message_01",
+ bucket_id: "cfr_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyCFRPolicy(data);
+
+ assert.equal(pingType, "cfr");
+ assert.isUndefined(ping.client_id);
+ assert.propertyVal(ping, "impression_id", FAKE_UUID);
+ assert.propertyVal(ping, "message_id", "n/a");
+ assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
+ });
+ it("should use client_id and message_id in the experiment cohort in Private Browsing", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "release";
+ },
+ });
+ sandbox.stub(ExperimentAPI, "getExperimentMetaData").returns({
+ slug: "SOME-CFR-EXP",
+ });
+ const data = {
+ action: "cfr_user_event",
+ event: "IMPRESSION",
+ is_private: true,
+ message_id: "cfr_message_01",
+ bucket_id: "cfr_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyCFRPolicy(data);
+
+ assert.equal(pingType, "cfr");
+ assert.isUndefined(ping.impression_id);
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.propertyVal(ping, "bucket_id", "cfr_bucket_01");
+ assert.propertyVal(ping, "message_id", "cfr_message_01");
+ });
+ });
+ describe("#applyWhatsNewPolicy", () => {
+ it("should set client_id and set pingType", async () => {
+ const { ping, pingType } = await instance.applyWhatsNewPolicy({});
+
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.equal(pingType, "whats-new-panel");
+ });
+ });
+ describe("#applyInfoBarPolicy", () => {
+ it("should set client_id and set pingType", async () => {
+ const { ping, pingType } = await instance.applyInfoBarPolicy({});
+
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.equal(pingType, "infobar");
+ });
+ });
+ describe("#applyToastNotificationPolicy", () => {
+ it("should set client_id and set pingType", async () => {
+ const { ping, pingType } = await instance.applyToastNotificationPolicy(
+ {}
+ );
+
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.equal(pingType, "toast_notification");
+ });
+ });
+ describe("#applySpotlightPolicy", () => {
+ it("should set client_id and set pingType", async () => {
+ let pingData = { action: "foo" };
+ const { ping, pingType } = await instance.applySpotlightPolicy(pingData);
+
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.equal(pingType, "spotlight");
+ assert.notProperty(ping, "action");
+ });
+ });
+ describe("#applyMomentsPolicy", () => {
+ it("should use client_id and message_id in prerelease", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "nightly";
+ },
+ });
+ const data = {
+ action: "moments_user_event",
+ event: "IMPRESSION",
+ message_id: "moments_message_01",
+ bucket_id: "moments_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyMomentsPolicy(data);
+
+ assert.equal(pingType, "moments");
+ assert.isUndefined(ping.impression_id);
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.propertyVal(ping, "bucket_id", "moments_bucket_01");
+ assert.propertyVal(ping, "message_id", "moments_message_01");
+ });
+ it("should use impression_id and bucket_id in release", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "release";
+ },
+ });
+ const data = {
+ action: "moments_user_event",
+ event: "IMPRESSION",
+ message_id: "moments_message_01",
+ bucket_id: "moments_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyMomentsPolicy(data);
+
+ assert.equal(pingType, "moments");
+ assert.isUndefined(ping.client_id);
+ assert.propertyVal(ping, "impression_id", FAKE_UUID);
+ assert.propertyVal(ping, "message_id", "n/a");
+ assert.propertyVal(ping, "bucket_id", "moments_bucket_01");
+ });
+ it("should use client_id and message_id in the experiment cohort in release", async () => {
+ globals.set("UpdateUtils", {
+ getUpdateChannel() {
+ return "release";
+ },
+ });
+ sandbox.stub(ExperimentAPI, "getExperimentMetaData").returns({
+ slug: "SOME-CFR-EXP",
+ });
+ const data = {
+ action: "moments_user_event",
+ event: "IMPRESSION",
+ message_id: "moments_message_01",
+ bucket_id: "moments_bucket_01",
+ };
+ const { ping, pingType } = await instance.applyMomentsPolicy(data);
+
+ assert.equal(pingType, "moments");
+ assert.isUndefined(ping.impression_id);
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.propertyVal(ping, "bucket_id", "moments_bucket_01");
+ assert.propertyVal(ping, "message_id", "moments_message_01");
+ });
+ });
+ describe("#applySnippetsPolicy", () => {
+ it("should include client_id", async () => {
+ const data = {
+ action: "snippets_user_event",
+ event: "IMPRESSION",
+ message_id: "snippets_message_01",
+ };
+ const { ping, pingType } = await instance.applySnippetsPolicy(data);
+
+ assert.equal(pingType, "snippets");
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.propertyVal(ping, "message_id", "snippets_message_01");
+ });
+ });
+ describe("#applyOnboardingPolicy", () => {
+ it("should include client_id", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "CLICK_BUTTION",
+ message_id: "onboarding_message_01",
+ };
+ const { ping, pingType } = await instance.applyOnboardingPolicy(data);
+
+ assert.equal(pingType, "onboarding");
+ assert.propertyVal(ping, "client_id", FAKE_TELEMETRY_ID);
+ assert.propertyVal(ping, "message_id", "onboarding_message_01");
+ assert.propertyVal(ping, "browser_session_id", "fake_session_id");
+ });
+ it("should include page to event_context if there is a session", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "CLICK_BUTTION",
+ message_id: "onboarding_message_01",
+ };
+ const session = { page: "about:welcome" };
+ const { ping, pingType } = await instance.applyOnboardingPolicy(
+ data,
+ session
+ );
+
+ assert.equal(pingType, "onboarding");
+ assert.propertyVal(
+ ping,
+ "event_context",
+ JSON.stringify({ page: "about:welcome" })
+ );
+ assert.propertyVal(ping, "message_id", "onboarding_message_01");
+ });
+ it("should not set page if it is not in ONBOARDING_ALLOWED_PAGE_VALUES", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "CLICK_BUTTION",
+ message_id: "onboarding_message_01",
+ };
+ const session = { page: "foo" };
+ const { ping, pingType } = await instance.applyOnboardingPolicy(
+ data,
+ session
+ );
+
+ assert.calledOnce(global.console.error);
+ assert.equal(pingType, "onboarding");
+ assert.propertyVal(ping, "event_context", JSON.stringify({}));
+ assert.propertyVal(ping, "message_id", "onboarding_message_01");
+ });
+ it("should append page to event_context if it is not empty", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "CLICK_BUTTION",
+ message_id: "onboarding_message_01",
+ event_context: JSON.stringify({ foo: "bar" }),
+ };
+ const session = { page: "about:welcome" };
+ const { ping, pingType } = await instance.applyOnboardingPolicy(
+ data,
+ session
+ );
+
+ assert.equal(pingType, "onboarding");
+ assert.propertyVal(
+ ping,
+ "event_context",
+ JSON.stringify({ foo: "bar", page: "about:welcome" })
+ );
+ assert.propertyVal(ping, "message_id", "onboarding_message_01");
+ });
+ it("should append page to event_context if it is not a JSON serialized string", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "CLICK_BUTTION",
+ message_id: "onboarding_message_01",
+ event_context: "foo",
+ };
+ const session = { page: "about:welcome" };
+ const { ping, pingType } = await instance.applyOnboardingPolicy(
+ data,
+ session
+ );
+
+ assert.equal(pingType, "onboarding");
+ assert.propertyVal(
+ ping,
+ "event_context",
+ JSON.stringify({ value: "foo", page: "about:welcome" })
+ );
+ assert.propertyVal(ping, "message_id", "onboarding_message_01");
+ });
+ });
+ describe("#applyUndesiredEventPolicy", () => {
+ it("should exclude client_id and use impression_id", () => {
+ const data = {
+ action: "asrouter_undesired_event",
+ event: "RS_MISSING_DATA",
+ };
+ const { ping, pingType } = instance.applyUndesiredEventPolicy(data);
+
+ assert.equal(pingType, "undesired-events");
+ assert.isUndefined(ping.client_id);
+ assert.propertyVal(ping, "impression_id", FAKE_UUID);
+ });
+ });
+ describe("#createASRouterEvent", () => {
+ it("should create a valid AS Router event", async () => {
+ const data = {
+ action: "snippets_user_event",
+ event: "CLICK",
+ message_id: "snippets_message_01",
+ };
+ const action = ac.ASRouterUserEvent(data);
+ const { ping } = await instance.createASRouterEvent(action);
+
+ assert.validate(ping, ASRouterEventPing);
+ assert.propertyVal(ping, "event", "CLICK");
+ });
+ it("should call applyCFRPolicy if action equals to cfr_user_event", async () => {
+ const data = {
+ action: "cfr_user_event",
+ event: "IMPRESSION",
+ message_id: "cfr_message_01",
+ };
+ sandbox.stub(instance, "applyCFRPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applyCFRPolicy);
+ });
+ it("should call applySnippetsPolicy if action equals to snippets_user_event", async () => {
+ const data = {
+ action: "snippets_user_event",
+ event: "IMPRESSION",
+ message_id: "snippets_message_01",
+ };
+ sandbox.stub(instance, "applySnippetsPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applySnippetsPolicy);
+ });
+ it("should call applySnippetsPolicy if action equals to snippets_local_testing_user_event", async () => {
+ const data = {
+ action: "snippets_local_testing_user_event",
+ event: "IMPRESSION",
+ message_id: "snippets_message_01",
+ };
+ sandbox.stub(instance, "applySnippetsPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applySnippetsPolicy);
+ });
+ it("should call applyOnboardingPolicy if action equals to onboarding_user_event", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "CLICK_BUTTON",
+ message_id: "onboarding_message_01",
+ };
+ sandbox.stub(instance, "applyOnboardingPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applyOnboardingPolicy);
+ });
+ it("should call applyWhatsNewPolicy if action equals to whats-new-panel_user_event", async () => {
+ const data = {
+ action: "whats-new-panel_user_event",
+ event: "CLICK_BUTTON",
+ message_id: "whats-new-panel_message_01",
+ };
+ sandbox.stub(instance, "applyWhatsNewPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applyWhatsNewPolicy);
+ });
+ it("should call applyMomentsPolicy if action equals to moments_user_event", async () => {
+ const data = {
+ action: "moments_user_event",
+ event: "CLICK_BUTTON",
+ message_id: "moments_message_01",
+ };
+ sandbox.stub(instance, "applyMomentsPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applyMomentsPolicy);
+ });
+ it("should call applySpotlightPolicy if action equals to spotlight_user_event", async () => {
+ const data = {
+ action: "spotlight_user_event",
+ event: "CLICK",
+ message_id: "SPOTLIGHT_MESSAGE_93",
+ };
+ sandbox.stub(instance, "applySpotlightPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applySpotlightPolicy);
+ });
+ it("should call applyToastNotificationPolicy if action equals to toast_notification_user_event", async () => {
+ const data = {
+ action: "toast_notification_user_event",
+ event: "IMPRESSION",
+ message_id: "TEST_TOAST_NOTIFICATION1",
+ };
+ sandbox.stub(instance, "applyToastNotificationPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applyToastNotificationPolicy);
+ });
+ it("should call applyUndesiredEventPolicy if action equals to asrouter_undesired_event", async () => {
+ const data = {
+ action: "asrouter_undesired_event",
+ event: "UNDESIRED_EVENT",
+ };
+ sandbox.stub(instance, "applyUndesiredEventPolicy");
+ const action = ac.ASRouterUserEvent(data);
+ await instance.createASRouterEvent(action);
+
+ assert.calledOnce(instance.applyUndesiredEventPolicy);
+ });
+ it("should stringify event_context if it is an Object", async () => {
+ const data = {
+ action: "asrouter_undesired_event",
+ event: "UNDESIRED_EVENT",
+ event_context: { foo: "bar" },
+ };
+ const action = ac.ASRouterUserEvent(data);
+ const { ping } = await instance.createASRouterEvent(action);
+
+ assert.propertyVal(ping, "event_context", JSON.stringify({ foo: "bar" }));
+ });
+ it("should not stringify event_context if it is a String", async () => {
+ const data = {
+ action: "asrouter_undesired_event",
+ event: "UNDESIRED_EVENT",
+ event_context: "foo",
+ };
+ const action = ac.ASRouterUserEvent(data);
+ const { ping } = await instance.createASRouterEvent(action);
+
+ assert.propertyVal(ping, "event_context", "foo");
+ });
+ });
+ describe("#sendEventPing", () => {
+ it("should call sendStructuredIngestionEvent", async () => {
+ const data = {
+ action: "activity_stream_user_event",
+ event: "CLICK",
+ };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+
+ await instance.sendEventPing(data);
+
+ const expectedPayload = {
+ client_id: FAKE_TELEMETRY_ID,
+ event: "CLICK",
+ browser_session_id: "fake_session_id",
+ };
+ assert.calledWith(instance.sendStructuredIngestionEvent, expectedPayload);
+ });
+ it("should stringify value if it is an Object", async () => {
+ const data = {
+ action: "activity_stream_user_event",
+ event: "CLICK",
+ value: { foo: "bar" },
+ };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+
+ await instance.sendEventPing(data);
+
+ const expectedPayload = {
+ client_id: FAKE_TELEMETRY_ID,
+ event: "CLICK",
+ browser_session_id: "fake_session_id",
+ value: JSON.stringify({ foo: "bar" }),
+ };
+ assert.calledWith(instance.sendStructuredIngestionEvent, expectedPayload);
+ });
+ });
+ describe("#sendSessionPing", () => {
+ it("should call sendStructuredIngestionEvent", async () => {
+ const data = {
+ action: "activity_stream_session",
+ page: "about:home",
+ session_duration: 10000,
+ };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+
+ await instance.sendSessionPing(data);
+
+ const expectedPayload = {
+ client_id: FAKE_TELEMETRY_ID,
+ page: "about:home",
+ session_duration: 10000,
+ };
+ assert.calledWith(instance.sendStructuredIngestionEvent, expectedPayload);
+ });
+ });
+ describe("#sendEvent", () => {
+ it("should call sendEventPing on activity_stream_user_event", () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ const event = { action: "activity_stream_user_event" };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendEventPing");
+
+ instance.sendEvent(event);
+
+ assert.calledOnce(instance.sendEventPing);
+ });
+ it("should call sendSessionPing on activity_stream_session", () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ const event = { action: "activity_stream_session" };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendSessionPing");
+
+ instance.sendEvent(event);
+
+ assert.calledOnce(instance.sendSessionPing);
+ });
+ });
+ describe("#sendUTEvent", () => {
+ it("should call the UT event function passed in", async () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ FAKE_GLOBAL_PREFS.set(EVENTS_TELEMETRY_PREF, true);
+ const event = {};
+ instance = new TelemetryFeed();
+ sandbox.stub(instance.utEvents, "sendUserEvent");
+
+ await instance.sendUTEvent(event, instance.utEvents.sendUserEvent);
+
+ assert.calledWith(instance.utEvents.sendUserEvent, event);
+ });
+ });
+ describe("#sendStructuredIngestionEvent", () => {
+ it("should call PingCentre sendStructuredIngestionPing", async () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ const event = {};
+ instance = new TelemetryFeed();
+ sandbox.stub(instance.pingCentre, "sendStructuredIngestionPing");
+
+ await instance.sendStructuredIngestionEvent(
+ event,
+ "http://foo.com/base/"
+ );
+
+ assert.calledWith(instance.pingCentre.sendStructuredIngestionPing, event);
+ });
+ });
+ describe("#setLoadTriggerInfo", () => {
+ it("should call saveSessionPerfData w/load_trigger_{ts,type} data", () => {
+ sandbox.stub(global.Cu, "now").returns(12345);
+
+ globals.set("ChromeUtils", {
+ addProfilerMarker: sandbox.stub(),
+ });
+
+ instance.browserOpenNewtabStart();
+
+ const stub = sandbox.stub(instance, "saveSessionPerfData");
+ instance.addSession("port123");
+
+ instance.setLoadTriggerInfo("port123");
+
+ assert.calledWith(stub, "port123", {
+ load_trigger_ts: 1588010448000 + 12345,
+ load_trigger_type: "menu_plus_or_keyboard",
+ });
+ });
+
+ it("should not call saveSessionPerfData when getting mark throws", () => {
+ const stub = sandbox.stub(instance, "saveSessionPerfData");
+ instance.addSession("port123");
+
+ instance.setLoadTriggerInfo("port123");
+
+ assert.notCalled(stub);
+ });
+ });
+
+ describe("#saveSessionPerfData", () => {
+ it("should update the given session with the given data", () => {
+ instance.addSession("port123");
+ assert.notProperty(instance.sessions.get("port123"), "fake_ts");
+ const data = { fake_ts: 456, other_fake_ts: 789 };
+
+ instance.saveSessionPerfData("port123", data);
+
+ assert.include(instance.sessions.get("port123").perf, data);
+ });
+
+ it("should call setLoadTriggerInfo if data has visibility_event_rcvd_ts", () => {
+ sandbox.stub(instance, "setLoadTriggerInfo");
+ instance.addSession("port123");
+ const data = { visibility_event_rcvd_ts: 444455 };
+
+ instance.saveSessionPerfData("port123", data);
+
+ assert.calledOnce(instance.setLoadTriggerInfo);
+ assert.calledWithExactly(instance.setLoadTriggerInfo, "port123");
+ assert.include(instance.sessions.get("port123").perf, data);
+ });
+
+ it("shouldn't call setLoadTriggerInfo if data has no visibility_event_rcvd_ts", () => {
+ sandbox.stub(instance, "setLoadTriggerInfo");
+ instance.addSession("port123");
+
+ instance.saveSessionPerfData("port123", { monkeys_ts: 444455 });
+
+ assert.notCalled(instance.setLoadTriggerInfo);
+ });
+
+ it("should not call setLoadTriggerInfo when url is about:home", () => {
+ sandbox.stub(instance, "setLoadTriggerInfo");
+ instance.addSession("port123", "about:home");
+ const data = { visibility_event_rcvd_ts: 444455 };
+
+ instance.saveSessionPerfData("port123", data);
+
+ assert.notCalled(instance.setLoadTriggerInfo);
+ });
+
+ it("should call maybeRecordTopsitesPainted when url is about:home and topsites_first_painted_ts is given", () => {
+ const topsites_first_painted_ts = 44455;
+ const data = { topsites_first_painted_ts };
+ const spy = sandbox.spy();
+
+ sandbox.stub(Services.prefs, "getIntPref").returns(1);
+ globals.set("AboutNewTab", {
+ maybeRecordTopsitesPainted: spy,
+ });
+ instance.addSession("port123", "about:home");
+ instance.saveSessionPerfData("port123", data);
+
+ assert.calledOnce(spy);
+ assert.calledWith(spy, topsites_first_painted_ts);
+ });
+ it("should record a Glean newtab.opened event with the correct visit_id when visibility event received", () => {
+ const session_id = "decafc0ffee";
+ const page = "about:newtab";
+ const session = { page, perf: {}, session_id };
+ const data = { visibility_event_rcvd_ts: 444455 };
+ sandbox.stub(instance.sessions, "get").returns(session);
+
+ sandbox.spy(Glean.newtab.opened, "record");
+ instance.saveSessionPerfData("port123", data);
+
+ assert.calledOnce(Glean.newtab.opened.record);
+ assert.deepEqual(Glean.newtab.opened.record.firstCall.args[0], {
+ newtab_visit_id: session_id,
+ source: page,
+ });
+ });
+ });
+ describe("#uninit", () => {
+ it("should call .pingCentre.uninit", () => {
+ const stub = sandbox.stub(instance.pingCentre, "uninit");
+
+ instance.uninit();
+
+ assert.calledOnce(stub);
+ });
+ it("should call .utEvents.uninit", () => {
+ const stub = sandbox.stub(instance.utEvents, "uninit");
+
+ instance.uninit();
+
+ assert.calledOnce(stub);
+ });
+ it("should make this.browserOpenNewtabStart() stop observing browser-open-newtab-start and domwindowopened", async () => {
+ await instance.init();
+ sandbox.spy(Services.obs, "removeObserver");
+ sandbox.stub(instance.pingCentre, "uninit");
+
+ await instance.uninit();
+
+ assert.calledTwice(Services.obs.removeObserver);
+ assert.calledWithExactly(
+ Services.obs.removeObserver,
+ instance.browserOpenNewtabStart,
+ "browser-open-newtab-start"
+ );
+ assert.calledWithExactly(
+ Services.obs.removeObserver,
+ instance._addWindowListeners,
+ "domwindowopened"
+ );
+ });
+ });
+ describe("#onAction", () => {
+ beforeEach(() => {
+ FAKE_GLOBAL_PREFS.clear();
+ });
+ it("should call .init() on an INIT action", () => {
+ const init = sandbox.stub(instance, "init");
+ const sendPageTakeoverData = sandbox.stub(
+ instance,
+ "sendPageTakeoverData"
+ );
+
+ instance.onAction({ type: at.INIT });
+
+ assert.calledOnce(init);
+ assert.calledOnce(sendPageTakeoverData);
+ });
+ it("should call .uninit() on an UNINIT action", () => {
+ const stub = sandbox.stub(instance, "uninit");
+
+ instance.onAction({ type: at.UNINIT });
+
+ assert.calledOnce(stub);
+ });
+ it("should call .handleNewTabInit on a NEW_TAB_INIT action", () => {
+ sandbox.spy(instance, "handleNewTabInit");
+
+ instance.onAction(
+ ac.AlsoToMain({
+ type: at.NEW_TAB_INIT,
+ data: { url: "about:newtab", browser },
+ })
+ );
+
+ assert.calledOnce(instance.handleNewTabInit);
+ });
+ it("should call .addSession() on a NEW_TAB_INIT action", () => {
+ const stub = sandbox.stub(instance, "addSession").returns({ perf: {} });
+ sandbox.stub(instance, "setLoadTriggerInfo");
+
+ instance.onAction(
+ ac.AlsoToMain(
+ {
+ type: at.NEW_TAB_INIT,
+ data: { url: "about:monkeys", browser },
+ },
+ "port123"
+ )
+ );
+
+ assert.calledOnce(stub);
+ assert.calledWith(stub, "port123", "about:monkeys");
+ });
+ it("should call .endSession() on a NEW_TAB_UNLOAD action", () => {
+ const stub = sandbox.stub(instance, "endSession");
+
+ instance.onAction(ac.AlsoToMain({ type: at.NEW_TAB_UNLOAD }, "port123"));
+
+ assert.calledWith(stub, "port123");
+ });
+ it("should call .saveSessionPerfData on SAVE_SESSION_PERF_DATA", () => {
+ const stub = sandbox.stub(instance, "saveSessionPerfData");
+ const data = { some_ts: 10 };
+ const action = { type: at.SAVE_SESSION_PERF_DATA, data };
+
+ instance.onAction(ac.AlsoToMain(action, "port123"));
+
+ assert.calledWith(stub, "port123", data);
+ });
+ it("should send an event on a TELEMETRY_USER_EVENT action", () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ FAKE_GLOBAL_PREFS.set(EVENTS_TELEMETRY_PREF, true);
+ instance = new TelemetryFeed();
+
+ const sendEvent = sandbox.stub(instance, "sendEvent");
+ const utSendUserEvent = sandbox.stub(instance.utEvents, "sendUserEvent");
+ const eventCreator = sandbox.stub(instance, "createUserEvent");
+ const action = { type: at.TELEMETRY_USER_EVENT };
+
+ instance.onAction(action);
+
+ assert.calledWith(eventCreator, action);
+ assert.calledWith(sendEvent, eventCreator.returnValue);
+ assert.calledWith(utSendUserEvent, eventCreator.returnValue);
+ });
+ it("should send an event on a DISCOVERY_STREAM_USER_EVENT action", () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ FAKE_GLOBAL_PREFS.set(EVENTS_TELEMETRY_PREF, true);
+ instance = new TelemetryFeed();
+
+ const sendEvent = sandbox.stub(instance, "sendEvent");
+ const utSendUserEvent = sandbox.stub(instance.utEvents, "sendUserEvent");
+ const eventCreator = sandbox.stub(instance, "createUserEvent");
+ const action = { type: at.DISCOVERY_STREAM_USER_EVENT };
+
+ instance.onAction(action);
+
+ assert.calledWith(eventCreator, {
+ ...action,
+ data: {
+ value: {
+ pocket_logged_in_status: true,
+ },
+ },
+ });
+ assert.calledWith(sendEvent, eventCreator.returnValue);
+ assert.calledWith(utSendUserEvent, eventCreator.returnValue);
+ });
+ describe("should call handleASRouterUserEvent on x action", () => {
+ const actions = [
+ at.AS_ROUTER_TELEMETRY_USER_EVENT,
+ msg.TOOLBAR_BADGE_TELEMETRY,
+ msg.TOOLBAR_PANEL_TELEMETRY,
+ msg.MOMENTS_PAGE_TELEMETRY,
+ msg.DOORHANGER_TELEMETRY,
+ ];
+ actions.forEach(type => {
+ it(`${type} action`, () => {
+ FAKE_GLOBAL_PREFS.set(TELEMETRY_PREF, true);
+ FAKE_GLOBAL_PREFS.set(EVENTS_TELEMETRY_PREF, true);
+ instance = new TelemetryFeed();
+
+ const eventHandler = sandbox.spy(instance, "handleASRouterUserEvent");
+ const action = {
+ type,
+ data: { event: "CLICK" },
+ };
+
+ instance.onAction(action);
+
+ assert.calledWith(eventHandler, action);
+ });
+ });
+ });
+ it("should send an event on a TELEMETRY_IMPRESSION_STATS action", () => {
+ const sendEvent = sandbox.stub(instance, "sendStructuredIngestionEvent");
+ const eventCreator = sandbox.stub(instance, "createImpressionStats");
+ const tiles = [{ id: 10001 }, { id: 10002 }, { id: 10003 }];
+ const action = ac.ImpressionStats({ source: "POCKET", tiles });
+
+ instance.onAction(action);
+
+ assert.calledWith(
+ eventCreator,
+ au.getPortIdOfSender(action),
+ action.data
+ );
+ assert.calledWith(sendEvent, eventCreator.returnValue);
+ });
+ it("should call .handleDiscoveryStreamImpressionStats on a DISCOVERY_STREAM_IMPRESSION_STATS action", () => {
+ const session = {};
+ sandbox.stub(instance.sessions, "get").returns(session);
+ const data = { source: "foo", tiles: [{ id: 1 }] };
+ const action = { type: at.DISCOVERY_STREAM_IMPRESSION_STATS, data };
+ sandbox.spy(instance, "handleDiscoveryStreamImpressionStats");
+
+ instance.onAction(ac.AlsoToMain(action, "port123"));
+
+ assert.calledWith(
+ instance.handleDiscoveryStreamImpressionStats,
+ "port123",
+ data
+ );
+ });
+ it("should call .handleDiscoveryStreamLoadedContent on a DISCOVERY_STREAM_LOADED_CONTENT action", () => {
+ const session = {};
+ sandbox.stub(instance.sessions, "get").returns(session);
+ const data = { source: "foo", tiles: [{ id: 1 }] };
+ const action = { type: at.DISCOVERY_STREAM_LOADED_CONTENT, data };
+ sandbox.spy(instance, "handleDiscoveryStreamLoadedContent");
+
+ instance.onAction(ac.AlsoToMain(action, "port123"));
+
+ assert.calledWith(
+ instance.handleDiscoveryStreamLoadedContent,
+ "port123",
+ data
+ );
+ });
+ it("should call .handleTopSitesSponsoredImpressionStats on a TOP_SITES_SPONSORED_IMPRESSION_STATS action", () => {
+ const session = {};
+ sandbox.stub(instance.sessions, "get").returns(session);
+ const data = { type: "impression", tile_id: 42, position: 1 };
+ const action = { type: at.TOP_SITES_SPONSORED_IMPRESSION_STATS, data };
+ sandbox.spy(instance, "handleTopSitesSponsoredImpressionStats");
+
+ instance.onAction(ac.AlsoToMain(action));
+
+ assert.calledOnce(instance.handleTopSitesSponsoredImpressionStats);
+ assert.deepEqual(
+ instance.handleTopSitesSponsoredImpressionStats.firstCall.args[0].data,
+ data
+ );
+ });
+ });
+ it("should call .handleTopSitesOrganicImpressionStats on a TOP_SITES_ORGANIC_IMPRESSION_STATS action", () => {
+ const session = {};
+ sandbox.stub(instance.sessions, "get").returns(session);
+ const data = { type: "impression", position: 1 };
+ const action = { type: at.TOP_SITES_ORGANIC_IMPRESSION_STATS, data };
+ sandbox.spy(instance, "handleTopSitesOrganicImpressionStats");
+
+ instance.onAction(ac.AlsoToMain(action));
+
+ assert.calledOnce(instance.handleTopSitesOrganicImpressionStats);
+ assert.deepEqual(
+ instance.handleTopSitesOrganicImpressionStats.firstCall.args[0].data,
+ data
+ );
+ });
+ describe("#handleNewTabInit", () => {
+ it("should set the session as preloaded if the browser is preloaded", () => {
+ const session = { perf: {} };
+ let preloadedBrowser = {
+ getAttribute() {
+ return "preloaded";
+ },
+ };
+ sandbox.stub(instance, "addSession").returns(session);
+
+ instance.onAction(
+ ac.AlsoToMain({
+ type: at.NEW_TAB_INIT,
+ data: { url: "about:newtab", browser: preloadedBrowser },
+ })
+ );
+
+ assert.ok(session.perf.is_preloaded);
+ });
+ it("should set the session as non-preloaded if the browser is non-preloaded", () => {
+ const session = { perf: {} };
+ let nonPreloadedBrowser = {
+ getAttribute() {
+ return "";
+ },
+ };
+ sandbox.stub(instance, "addSession").returns(session);
+
+ instance.onAction(
+ ac.AlsoToMain({
+ type: at.NEW_TAB_INIT,
+ data: { url: "about:newtab", browser: nonPreloadedBrowser },
+ })
+ );
+
+ assert.ok(!session.perf.is_preloaded);
+ });
+ });
+ describe("#SendASRouterUndesiredEvent", () => {
+ it("should call handleASRouterUserEvent", () => {
+ let stub = sandbox.stub(instance, "handleASRouterUserEvent");
+
+ instance.SendASRouterUndesiredEvent({ foo: "bar" });
+
+ assert.calledOnce(stub);
+ let [payload] = stub.firstCall.args;
+ assert.propertyVal(payload.data, "action", "asrouter_undesired_event");
+ assert.propertyVal(payload.data, "foo", "bar");
+ });
+ });
+ describe("#sendPageTakeoverData", () => {
+ let fakePrefs = { "browser.newtabpage.enabled": true };
+
+ beforeEach(() => {
+ globals.set(
+ "Services",
+ Object.assign({}, Services, {
+ prefs: { getBoolPref: key => fakePrefs[key] },
+ })
+ );
+ // Services.prefs = {getBoolPref: key => fakePrefs[key]};
+ sandbox.spy(Glean.newtab.newtabCategory, "set");
+ sandbox.spy(Glean.newtab.homepageCategory, "set");
+ });
+ it("should send correct event data for about:home set to custom URL", async () => {
+ fakeHomePageUrl = "https://searchprovider.com";
+ instance._prefs.set(TELEMETRY_PREF, true);
+ instance._classifySite = () => Promise.resolve("other");
+ const sendEvent = sandbox.stub(instance, "sendEvent");
+
+ await instance.sendPageTakeoverData();
+ assert.calledOnce(sendEvent);
+ assert.equal(sendEvent.firstCall.args[0].event, "PAGE_TAKEOVER_DATA");
+ assert.deepEqual(sendEvent.firstCall.args[0].value, {
+ home_url_category: "other",
+ });
+ assert.validate(sendEvent.firstCall.args[0], UserEventPing);
+ assert.calledOnce(Glean.newtab.homepageCategory.set);
+ assert.calledWith(Glean.newtab.homepageCategory.set, "other");
+ });
+ it("should send correct event data for about:newtab set to custom URL", async () => {
+ globals.set("AboutNewTab", {
+ newTabURLOverridden: true,
+ newTabURL: "https://searchprovider.com",
+ });
+ instance._prefs.set(TELEMETRY_PREF, true);
+ instance._classifySite = () => Promise.resolve("other");
+ const sendEvent = sandbox.stub(instance, "sendEvent");
+
+ await instance.sendPageTakeoverData();
+ assert.calledOnce(sendEvent);
+ assert.equal(sendEvent.firstCall.args[0].event, "PAGE_TAKEOVER_DATA");
+ assert.deepEqual(sendEvent.firstCall.args[0].value, {
+ newtab_url_category: "other",
+ });
+ assert.validate(sendEvent.firstCall.args[0], UserEventPing);
+ assert.calledOnce(Glean.newtab.newtabCategory.set);
+ assert.calledWith(Glean.newtab.newtabCategory.set, "other");
+ });
+ it("should not send an event if neither about:{home,newtab} are set to custom URL", async () => {
+ instance._prefs.set(TELEMETRY_PREF, true);
+ const sendEvent = sandbox.stub(instance, "sendEvent");
+
+ await instance.sendPageTakeoverData();
+ assert.notCalled(sendEvent);
+ assert.calledOnce(Glean.newtab.newtabCategory.set);
+ assert.calledOnce(Glean.newtab.homepageCategory.set);
+ assert.calledWith(Glean.newtab.newtabCategory.set, "enabled");
+ assert.calledWith(Glean.newtab.homepageCategory.set, "enabled");
+ });
+ it("should send home_extension_id and newtab_extension_id when appropriate", async () => {
+ const ID = "{abc-foo-bar}";
+ fakeExtensionSettingsStore.getSetting = () => ({ id: ID });
+ instance._prefs.set(TELEMETRY_PREF, true);
+ instance._classifySite = () => Promise.resolve("other");
+ const sendEvent = sandbox.stub(instance, "sendEvent");
+
+ await instance.sendPageTakeoverData();
+ assert.calledOnce(sendEvent);
+ assert.equal(sendEvent.firstCall.args[0].event, "PAGE_TAKEOVER_DATA");
+ assert.deepEqual(sendEvent.firstCall.args[0].value, {
+ home_extension_id: ID,
+ newtab_extension_id: ID,
+ });
+ assert.validate(sendEvent.firstCall.args[0], UserEventPing);
+ assert.calledOnce(Glean.newtab.newtabCategory.set);
+ assert.calledOnce(Glean.newtab.homepageCategory.set);
+ assert.equal(Glean.newtab.newtabCategory.set.args[0], "extension");
+ assert.equal(Glean.newtab.homepageCategory.set.args[0], "extension");
+ });
+ it("instruments when newtab is disabled", async () => {
+ instance._prefs.set(TELEMETRY_PREF, true);
+ fakePrefs["browser.newtabpage.enabled"] = false;
+ await instance.sendPageTakeoverData();
+ assert.calledOnce(Glean.newtab.newtabCategory.set);
+ assert.calledWith(Glean.newtab.newtabCategory.set, "disabled");
+ });
+ it("instruments when homepage is disabled", async () => {
+ instance._prefs.set(TELEMETRY_PREF, true);
+ fakeHomePage.overridden = true;
+ await instance.sendPageTakeoverData();
+ assert.calledOnce(Glean.newtab.homepageCategory.set);
+ assert.calledWith(Glean.newtab.homepageCategory.set, "disabled");
+ });
+ it("should send a 'newtab' ping", async () => {
+ instance._prefs.set(TELEMETRY_PREF, true);
+ sandbox.spy(GleanPings.newtab, "submit");
+ await instance.sendPageTakeoverData();
+ assert.calledOnce(GleanPings.newtab.submit);
+ assert.calledWithExactly(GleanPings.newtab.submit, "component_init");
+ });
+ });
+ describe("#sendDiscoveryStreamImpressions", () => {
+ it("should not send impression pings if there is no impression data", () => {
+ const spy = sandbox.spy(instance, "sendEvent");
+ const session = {};
+ instance.sendDiscoveryStreamImpressions("foo", session);
+
+ assert.notCalled(spy);
+ });
+ it("should send impression pings if there is impression data", () => {
+ const spy = sandbox.spy(instance, "sendStructuredIngestionEvent");
+ const session = {
+ impressionSets: {
+ source_foo: [
+ { id: 1, pos: 0 },
+ { id: 2, pos: 1 },
+ ],
+ source_bar: [
+ { id: 3, pos: 0 },
+ { id: 4, pos: 1 },
+ ],
+ },
+ };
+ instance.sendDiscoveryStreamImpressions("foo", session);
+
+ assert.calledTwice(spy);
+ });
+ });
+ describe("#sendDiscoveryStreamLoadedContent", () => {
+ it("should not send loaded content pings if there is no loaded content data", () => {
+ const spy = sandbox.spy(instance, "sendEvent");
+ const session = {};
+ instance.sendDiscoveryStreamLoadedContent("foo", session);
+
+ assert.notCalled(spy);
+ });
+ it("should send loaded content pings if there is loaded content data", () => {
+ const spy = sandbox.spy(instance, "sendStructuredIngestionEvent");
+ const session = {
+ loadedContentSets: {
+ source_foo: [
+ { id: 1, pos: 0 },
+ { id: 2, pos: 1 },
+ ],
+ source_bar: [
+ { id: 3, pos: 0 },
+ { id: 4, pos: 1 },
+ ],
+ },
+ };
+ instance.sendDiscoveryStreamLoadedContent("foo", session);
+
+ assert.calledTwice(spy);
+
+ let [payload] = spy.firstCall.args;
+ let sources = new Set([]);
+ sources.add(payload.source);
+ assert.equal(payload.loaded, 2);
+ assert.deepEqual(
+ payload.tiles,
+ session.loadedContentSets[payload.source]
+ );
+
+ [payload] = spy.secondCall.args;
+ sources.add(payload.source);
+ assert.equal(payload.loaded, 2);
+ assert.deepEqual(
+ payload.tiles,
+ session.loadedContentSets[payload.source]
+ );
+
+ assert.deepEqual(sources, new Set(["source_foo", "source_bar"]));
+ });
+ });
+ describe("#handleDiscoveryStreamImpressionStats", () => {
+ it("should throw for a missing session", () => {
+ assert.throws(() => {
+ instance.handleDiscoveryStreamImpressionStats("a_missing_port", {});
+ }, "Session does not exist.");
+ });
+ it("should store impression to impressionSets", () => {
+ const session = instance.addSession("new_session", "about:newtab");
+ instance.handleDiscoveryStreamImpressionStats("new_session", {
+ source: "foo",
+ tiles: [{ id: 1, pos: 0 }],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+
+ assert.equal(Object.keys(session.impressionSets).length, 1);
+ assert.deepEqual(session.impressionSets.foo, {
+ tiles: [{ id: 1, pos: 0 }],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+
+ // Add another ping with the same source
+ instance.handleDiscoveryStreamImpressionStats("new_session", {
+ source: "foo",
+ tiles: [{ id: 2, pos: 1 }],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+
+ assert.deepEqual(session.impressionSets.foo, {
+ tiles: [
+ { id: 1, pos: 0 },
+ { id: 2, pos: 1 },
+ ],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+
+ // Add another ping with a different source
+ instance.handleDiscoveryStreamImpressionStats("new_session", {
+ source: "bar",
+ tiles: [{ id: 3, pos: 2 }],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+
+ assert.equal(Object.keys(session.impressionSets).length, 2);
+ assert.deepEqual(session.impressionSets.foo, {
+ tiles: [
+ { id: 1, pos: 0 },
+ { id: 2, pos: 1 },
+ ],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+ assert.deepEqual(session.impressionSets.bar, {
+ tiles: [{ id: 3, pos: 2 }],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+ });
+ it("should instrument pocket impressions", () => {
+ const session_id = "1337cafe";
+ const pos1 = 1;
+ const pos2 = 4;
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.impression, "record");
+
+ instance.handleDiscoveryStreamImpressionStats("_", {
+ source: "foo",
+ tiles: [
+ { id: 1, pos: pos1, type: "organic" },
+ { id: 2, pos: pos2, type: "spoc" },
+ ],
+ window_inner_width: 1000,
+ window_inner_height: 900,
+ });
+
+ assert.calledTwice(Glean.pocket.impression.record);
+ assert.deepEqual(Glean.pocket.impression.record.firstCall.args[0], {
+ newtab_visit_id: session_id,
+ is_sponsored: false,
+ position: pos1,
+ });
+ assert.deepEqual(Glean.pocket.impression.record.secondCall.args[0], {
+ newtab_visit_id: session_id,
+ is_sponsored: true,
+ position: pos2,
+ });
+ });
+ });
+ describe("#handleDiscoveryStreamLoadedContent", () => {
+ it("should throw for a missing session", () => {
+ assert.throws(() => {
+ instance.handleDiscoveryStreamLoadedContent("a_missing_port", {});
+ }, "Session does not exist.");
+ });
+ it("should store loaded content to loadedContentSets", () => {
+ const session = instance.addSession("new_session", "about:newtab");
+ instance.handleDiscoveryStreamLoadedContent("new_session", {
+ source: "foo",
+ tiles: [{ id: 1, pos: 0 }],
+ });
+
+ assert.equal(Object.keys(session.loadedContentSets).length, 1);
+ assert.deepEqual(session.loadedContentSets.foo, [{ id: 1, pos: 0 }]);
+
+ // Add another ping with the same source
+ instance.handleDiscoveryStreamLoadedContent("new_session", {
+ source: "foo",
+ tiles: [{ id: 2, pos: 1 }],
+ });
+
+ assert.deepEqual(session.loadedContentSets.foo, [
+ { id: 1, pos: 0 },
+ { id: 2, pos: 1 },
+ ]);
+
+ // Add another ping with a different source
+ instance.handleDiscoveryStreamLoadedContent("new_session", {
+ source: "bar",
+ tiles: [{ id: 3, pos: 2 }],
+ });
+
+ assert.equal(Object.keys(session.loadedContentSets).length, 2);
+ assert.deepEqual(session.loadedContentSets.foo, [
+ { id: 1, pos: 0 },
+ { id: 2, pos: 1 },
+ ]);
+ assert.deepEqual(session.loadedContentSets.bar, [{ id: 3, pos: 2 }]);
+ });
+ });
+ describe("#_generateStructuredIngestionEndpoint", () => {
+ it("should generate a valid endpoint", () => {
+ const fakeEndpoint = "http://fakeendpoint.com/base/";
+ const fakeUUID = "{34f24486-f01a-9749-9c5b-21476af1fa77}";
+ const fakeUUIDWithoutBraces = fakeUUID.substring(1, fakeUUID.length - 1);
+ FAKE_GLOBAL_PREFS.set(STRUCTURED_INGESTION_ENDPOINT_PREF, fakeEndpoint);
+ sandbox.stub(Services.uuid, "generateUUID").returns(fakeUUID);
+ const feed = new TelemetryFeed();
+ const url = feed._generateStructuredIngestionEndpoint(
+ "testNameSpace",
+ "testPingType",
+ "1"
+ );
+
+ assert.equal(
+ url,
+ `${fakeEndpoint}/testNameSpace/testPingType/1/${fakeUUIDWithoutBraces}`
+ );
+ });
+ });
+ describe("#handleASRouterUserEvent", () => {
+ it("should call sendStructuredIngestionEvent on known pingTypes", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "IMPRESSION",
+ message_id: "12345",
+ };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+
+ await instance.handleASRouterUserEvent({ data });
+
+ assert.calledOnce(instance.sendStructuredIngestionEvent);
+ });
+ it("should call submitGleanPingForPing on known pingTypes when telemetry is enabled", async () => {
+ const data = {
+ action: "onboarding_user_event",
+ event: "IMPRESSION",
+ message_id: "12345",
+ };
+ instance = new TelemetryFeed();
+ instance._prefs.set(TELEMETRY_PREF, true);
+ sandbox.spy(
+ global.AboutWelcomeTelemetry.prototype,
+ "submitGleanPingForPing"
+ );
+
+ await instance.handleASRouterUserEvent({ data });
+
+ assert.calledOnce(
+ global.AboutWelcomeTelemetry.prototype.submitGleanPingForPing
+ );
+ });
+ it("should console.error and not submit pings on unknown pingTypes", async () => {
+ const data = {
+ action: "unknown_event",
+ event: "IMPRESSION",
+ message_id: "12345",
+ };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+
+ await instance.handleASRouterUserEvent({ data });
+
+ assert.calledOnce(global.console.error);
+ assert.notCalled(instance.sendStructuredIngestionEvent);
+ });
+ });
+ describe("#isInCFRCohort", () => {
+ it("should return false if there is no CFR experiment registered", () => {
+ assert.ok(!instance.isInCFRCohort);
+ });
+ it("should return true if there is a CFR experiment registered", () => {
+ sandbox.stub(ExperimentAPI, "getExperimentMetaData").returns({
+ slug: "SOME-CFR-EXP",
+ });
+
+ assert.ok(instance.isInCFRCohort);
+ assert.propertyVal(
+ ExperimentAPI.getExperimentMetaData.firstCall.args[0],
+ "featureId",
+ "cfr"
+ );
+ });
+ });
+ describe("#handleTopSitesSponsoredImpressionStats", () => {
+ it("should call sendStructuredIngestionEvent on an impression event", async () => {
+ const data = {
+ type: "impression",
+ tile_id: 42,
+ source: "newtab",
+ position: 0,
+ reporting_url: "https://test.reporting.net/",
+ };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+ sandbox.spy(Services.telemetry, "keyedScalarAdd");
+
+ await instance.handleTopSitesSponsoredImpressionStats({ data });
+
+ // Scalar should be added
+ assert.calledOnce(Services.telemetry.keyedScalarAdd);
+ assert.calledWith(
+ Services.telemetry.keyedScalarAdd,
+ "contextual.services.topsites.impression",
+ "newtab_1",
+ 1
+ );
+
+ assert.calledOnce(instance.sendStructuredIngestionEvent);
+
+ const { args } = instance.sendStructuredIngestionEvent.firstCall;
+ // payload
+ assert.deepEqual(args[0], {
+ context_id: FAKE_UUID,
+ tile_id: 42,
+ source: "newtab",
+ position: 1,
+ reporting_url: "https://test.reporting.net/",
+ });
+ // namespace
+ assert.equal(args[1], "contextual-services");
+ // docType
+ assert.equal(args[2], "topsites-impression");
+ // version
+ assert.equal(args[3], "1");
+ });
+ it("should call sendStructuredIngestionEvent on a click event", async () => {
+ const data = {
+ type: "click",
+ tile_id: 42,
+ source: "newtab",
+ position: 0,
+ reporting_url: "https://test.reporting.net/",
+ };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+ sandbox.spy(Services.telemetry, "keyedScalarAdd");
+
+ await instance.handleTopSitesSponsoredImpressionStats({ data });
+
+ // Scalar should be added
+ assert.calledOnce(Services.telemetry.keyedScalarAdd);
+ assert.calledWith(
+ Services.telemetry.keyedScalarAdd,
+ "contextual.services.topsites.click",
+ "newtab_1",
+ 1
+ );
+
+ assert.calledOnce(instance.sendStructuredIngestionEvent);
+
+ const { args } = instance.sendStructuredIngestionEvent.firstCall;
+ // payload
+ assert.deepEqual(args[0], {
+ context_id: FAKE_UUID,
+ tile_id: 42,
+ source: "newtab",
+ position: 1,
+ reporting_url: "https://test.reporting.net/",
+ });
+ // namespace
+ assert.equal(args[1], "contextual-services");
+ // docType
+ assert.equal(args[2], "topsites-click");
+ // version
+ assert.equal(args[3], "1");
+ });
+ it("should record a Glean topsites.impression event on an impression event", async () => {
+ const data = {
+ type: "impression",
+ tile_id: 42,
+ source: "newtab",
+ position: 1,
+ reporting_url: "https://test.reporting.net/",
+ advertiser: "adnoid ads",
+ };
+ instance = new TelemetryFeed();
+ const session_id = "decafc0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.topsites.impression, "record");
+
+ await instance.handleTopSitesSponsoredImpressionStats({ data });
+
+ // Event should be recorded
+ assert.calledOnce(Glean.topsites.impression.record);
+ assert.calledWith(Glean.topsites.impression.record, {
+ advertiser_name: "adnoid ads",
+ tile_id: "42",
+ newtab_visit_id: session_id,
+ is_sponsored: true,
+ position: 1,
+ });
+ });
+ it("should record a Glean topsites.click event on a click event", async () => {
+ const data = {
+ type: "click",
+ advertiser: "test advertiser",
+ tile_id: 42,
+ source: "newtab",
+ position: 0,
+ reporting_url: "https://test.reporting.net/",
+ };
+ instance = new TelemetryFeed();
+ const session_id = "decafc0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.topsites.click, "record");
+
+ await instance.handleTopSitesSponsoredImpressionStats({ data });
+
+ // Event should be recorded
+ assert.calledOnce(Glean.topsites.click.record);
+ assert.calledWith(Glean.topsites.click.record, {
+ advertiser_name: "test advertiser",
+ tile_id: "42",
+ newtab_visit_id: session_id,
+ is_sponsored: true,
+ position: 0,
+ });
+ });
+ it("should console.error on unknown pingTypes", async () => {
+ const data = { type: "unknown_type" };
+ instance = new TelemetryFeed();
+ sandbox.spy(instance, "sendStructuredIngestionEvent");
+
+ await instance.handleTopSitesSponsoredImpressionStats({ data });
+
+ assert.calledOnce(global.console.error);
+ assert.notCalled(instance.sendStructuredIngestionEvent);
+ });
+ });
+ describe("#handleTopSitesOrganicImpressionStats", () => {
+ it("should record a Glean topsites.impression event on an impression event", async () => {
+ const data = {
+ type: "impression",
+ source: "newtab",
+ position: 0,
+ };
+ instance = new TelemetryFeed();
+ const session_id = "decafc0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.topsites.impression, "record");
+
+ await instance.handleTopSitesOrganicImpressionStats({ data });
+
+ assert.calledOnce(Glean.topsites.impression.record);
+ assert.calledWith(Glean.topsites.impression.record, {
+ newtab_visit_id: session_id,
+ is_sponsored: false,
+ position: 0,
+ });
+ });
+ it("should record a Glean topsites.click event on a click event", async () => {
+ const data = {
+ type: "click",
+ source: "newtab",
+ position: 0,
+ };
+ instance = new TelemetryFeed();
+ const session_id = "decafc0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.topsites.click, "record");
+
+ await instance.handleTopSitesOrganicImpressionStats({ data });
+
+ assert.calledOnce(Glean.topsites.click.record);
+ assert.calledWith(Glean.topsites.click.record, {
+ newtab_visit_id: session_id,
+ is_sponsored: false,
+ position: 0,
+ });
+ });
+ });
+ describe("#handleDiscoveryStreamUserEvent", () => {
+ it("correctly handles action with no `data`", () => {
+ const action = ac.DiscoveryStreamUserEvent();
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.topicClick, "record");
+ sandbox.spy(Glean.pocket.click, "record");
+ sandbox.spy(Glean.pocket.save, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.notCalled(Glean.pocket.topicClick.record);
+ assert.notCalled(Glean.pocket.click.record);
+ assert.notCalled(Glean.pocket.save.record);
+ });
+ it("correctly handles CLICK data with no value", () => {
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "POPULAR_TOPICS",
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.topicClick, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.topicClick.record);
+ assert.calledWith(Glean.pocket.topicClick.record, {
+ newtab_visit_id: session_id,
+ topic: undefined,
+ });
+ });
+ it("correctly handles non-POPULAR_TOPICS CLICK data with no value", () => {
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "not-POPULAR_TOPICS",
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.topicClick, "record");
+ sandbox.spy(Glean.pocket.click, "record");
+ sandbox.spy(Glean.pocket.save, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.notCalled(Glean.pocket.topicClick.record);
+ assert.notCalled(Glean.pocket.click.record);
+ assert.notCalled(Glean.pocket.save.record);
+ });
+ it("correctly handles CLICK data with non-POPULAR_TOPICS source", () => {
+ const topic = "atopic";
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "not-POPULAR_TOPICS",
+ value: {
+ card_type: "topics_widget",
+ topic,
+ },
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.topicClick, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.topicClick.record);
+ assert.calledWith(Glean.pocket.topicClick.record, {
+ newtab_visit_id: session_id,
+ topic,
+ });
+ });
+ it("doesn't instrument a CLICK without a card_type", () => {
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "not-POPULAR_TOPICS",
+ value: {
+ card_type: "not spoc, organic, or topics_widget",
+ },
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.topicClick, "record");
+ sandbox.spy(Glean.pocket.click, "record");
+ sandbox.spy(Glean.pocket.save, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.notCalled(Glean.pocket.topicClick.record);
+ assert.notCalled(Glean.pocket.click.record);
+ assert.notCalled(Glean.pocket.save.record);
+ });
+ it("instruments a popular topic click", () => {
+ const topic = "entertainment";
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "POPULAR_TOPICS",
+ value: {
+ card_type: "topics_widget",
+ topic,
+ },
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.topicClick, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.topicClick.record);
+ assert.calledWith(Glean.pocket.topicClick.record, {
+ newtab_visit_id: session_id,
+ topic,
+ });
+ });
+ it("instruments an organic top stories click", () => {
+ const action_position = 42;
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ action_position,
+ value: {
+ card_type: "organic",
+ },
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.click, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.click.record);
+ assert.calledWith(Glean.pocket.click.record, {
+ newtab_visit_id: session_id,
+ is_sponsored: false,
+ position: action_position,
+ });
+ });
+ it("instruments a sponsored top stories click", () => {
+ const action_position = 42;
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ action_position,
+ value: {
+ card_type: "spoc",
+ },
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.click, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.click.record);
+ assert.calledWith(Glean.pocket.click.record, {
+ newtab_visit_id: session_id,
+ is_sponsored: true,
+ position: action_position,
+ });
+ });
+ it("instruments a save of an organic top story", () => {
+ const action_position = 42;
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "SAVE_TO_POCKET",
+ action_position,
+ value: {
+ card_type: "organic",
+ },
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.save, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.save.record);
+ assert.calledWith(Glean.pocket.save.record, {
+ newtab_visit_id: session_id,
+ is_sponsored: false,
+ position: action_position,
+ });
+ });
+ it("instruments a save of a sponsored top story", () => {
+ const action_position = 42;
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "SAVE_TO_POCKET",
+ action_position,
+ value: {
+ card_type: "spoc",
+ },
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.save, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.save.record);
+ assert.calledWith(Glean.pocket.save.record, {
+ newtab_visit_id: session_id,
+ is_sponsored: true,
+ position: action_position,
+ });
+ });
+ it("instruments a save of a sponsored top story, without `value`", () => {
+ const action_position = 42;
+ const action = ac.DiscoveryStreamUserEvent({
+ event: "SAVE_TO_POCKET",
+ action_position,
+ });
+ instance = new TelemetryFeed();
+ const session_id = "c0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id });
+ sandbox.spy(Glean.pocket.save, "record");
+
+ instance.handleDiscoveryStreamUserEvent(action);
+
+ assert.calledOnce(Glean.pocket.save.record);
+ assert.calledWith(Glean.pocket.save.record, {
+ newtab_visit_id: session_id,
+ is_sponsored: false,
+ position: action_position,
+ });
+ });
+ });
+});