fune/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
Chris H-C 6bf60bf55d Bug 1868580 - Remove PingCentre from Contextual Services r=nanj,perry.mcmanis
As of 11amPT December 7, Contextual Services is now looking exclusively at
Glean-sent data for Firefox versions 116+. (See DSRE-1489)

Differential Revision: https://phabricator.services.mozilla.com/D195910
2023-12-11 19:21:37 +00:00

2746 lines
94 KiB
JavaScript

/* 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() {}
}
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 record pref_changed events for topsites pref changes", () => {
FAKE_GLOBAL_PREFS.set(
"browser.newtabpage.activity-stream.feeds.topsites",
false
);
FAKE_GLOBAL_PREFS.set(
"browser.newtabpage.activity-stream.showSponsoredTopSites",
true
);
sandbox.spy(Glean.topsites.enabled, "set");
sandbox.spy(Glean.topsites.sponsoredEnabled, "set");
sandbox.spy(Glean.topsites.prefChanged, "record");
instance = new TelemetryFeed();
instance.init();
Services.prefs.setBoolPref(
"browser.newtabpage.activity-stream.feeds.topsites",
true
);
Services.prefs.setBoolPref(
"browser.newtabpage.activity-stream.showSponsoredTopSites",
false
);
assert.calledTwice(Glean.topsites.prefChanged.record);
assert.deepEqual(Glean.topsites.prefChanged.record.firstCall.args[0], {
pref_name: "browser.newtabpage.activity-stream.feeds.topsites",
new_value: true,
});
assert.deepEqual(Glean.topsites.prefChanged.record.secondCall.args[0], {
pref_name: "browser.newtabpage.activity-stream.showSponsoredTopSites",
new_value: false,
});
});
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("#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: "cfr_user_event",
event: "CLICK",
message_id: "cfr_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 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("#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 .handleAboutSponsoredTopSites on a ABOUT_SPONSORED_TOP_SITES action", () => {
const data = { position: 0, advertiser_name: "moo", tile_id: 42 };
const action = { type: at.ABOUT_SPONSORED_TOP_SITES, data };
sandbox.spy(instance, "handleAboutSponsoredTopSites");
instance.onAction(ac.AlsoToMain(action));
assert.calledOnce(instance.handleAboutSponsoredTopSites);
});
it("should call #handleBlockUrl on a BLOCK_URL action", () => {
const data = { position: 0, advertiser_name: "moo", tile_id: 42 };
const action = { type: at.BLOCK_URL, data };
sandbox.spy(instance, "handleBlockUrl");
instance.onAction(ac.AlsoToMain(action));
assert.calledOnce(instance.handleBlockUrl);
});
});
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;
const shim = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ=";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.pocket.impression, "record");
sandbox.spy(Glean.pocket.shim, "set");
sandbox.spy(GleanPings.spoc, "submit");
instance.handleDiscoveryStreamImpressionStats("_", {
source: "foo",
tiles: [
{
id: 1,
pos: pos1,
type: "organic",
recommendation_id: "decaf-c0ff33",
},
{
id: 2,
pos: pos2,
type: "spoc",
recommendation_id: undefined,
shim,
},
],
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,
recommendation_id: "decaf-c0ff33",
tile_id: 1,
});
assert.deepEqual(Glean.pocket.impression.record.secondCall.args[0], {
newtab_visit_id: session_id,
is_sponsored: true,
position: pos2,
recommendation_id: undefined,
tile_id: 2,
});
assert.calledOnce(Glean.pocket.shim.set);
assert.calledWith(Glean.pocket.shim.set, shim);
assert.calledOnce(GleanPings.spoc.submit);
assert.calledWith(GleanPings.spoc.submit, "impression");
});
});
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 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();
instance._prefs.set(TELEMETRY_PREF, true);
sandbox.spy(
global.AboutWelcomeTelemetry.prototype,
"submitGleanPingForPing"
);
await instance.handleASRouterUserEvent({ data });
assert.calledOnce(global.console.error);
assert.notCalled(
global.AboutWelcomeTelemetry.prototype.submitGleanPingForPing
);
});
});
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 add to keyed scalar 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(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
);
});
it("should add to keyed scalar 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(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
);
});
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: data.tile_id,
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: data.tile_id,
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",
recommendation_id: "decaf-c0ff33",
tile_id: 314623757745896,
},
});
instance = new TelemetryFeed();
const session_id = "c0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.pocket.click, "record");
sandbox.spy(Glean.pocket.shim, "set");
sandbox.spy(GleanPings.spoc, "submit");
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,
recommendation_id: "decaf-c0ff33",
tile_id: 314623757745896,
});
assert.notCalled(Glean.pocket.shim.set);
assert.notCalled(GleanPings.spoc.submit);
});
it("instruments a sponsored top stories click", () => {
const action_position = 42;
const shim = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ=";
const action = ac.DiscoveryStreamUserEvent({
event: "CLICK",
action_position,
value: {
card_type: "spoc",
recommendation_id: undefined,
tile_id: 448685088,
shim,
},
});
instance = new TelemetryFeed();
const session_id = "c0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.pocket.click, "record");
sandbox.spy(Glean.pocket.shim, "set");
sandbox.spy(GleanPings.spoc, "submit");
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,
recommendation_id: undefined,
tile_id: 448685088,
});
assert.calledOnce(Glean.pocket.shim.set);
assert.calledWith(Glean.pocket.shim.set, shim);
assert.calledOnce(GleanPings.spoc.submit);
assert.calledWith(GleanPings.spoc.submit, "click");
});
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",
recommendation_id: "decaf-c0ff33",
tile_id: 314623757745896,
},
});
instance = new TelemetryFeed();
const session_id = "c0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.pocket.save, "record");
sandbox.spy(Glean.pocket.shim, "set");
sandbox.spy(GleanPings.spoc, "submit");
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,
recommendation_id: "decaf-c0ff33",
tile_id: 314623757745896,
});
assert.notCalled(Glean.pocket.shim.set);
assert.notCalled(GleanPings.spoc.submit);
});
it("instruments a save of a sponsored top story", () => {
const action_position = 42;
const shim = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ=";
const action = ac.DiscoveryStreamUserEvent({
event: "SAVE_TO_POCKET",
action_position,
value: {
card_type: "spoc",
recommendation_id: undefined,
tile_id: 448685088,
shim,
},
});
instance = new TelemetryFeed();
const session_id = "c0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.pocket.save, "record");
sandbox.spy(Glean.pocket.shim, "set");
sandbox.spy(GleanPings.spoc, "submit");
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,
recommendation_id: undefined,
tile_id: 448685088,
});
assert.calledOnce(Glean.pocket.shim.set);
assert.calledWith(Glean.pocket.shim.set, shim);
assert.calledOnce(GleanPings.spoc.submit);
assert.calledWith(GleanPings.spoc.submit, "save");
});
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");
sandbox.spy(Glean.pocket.shim, "set");
sandbox.spy(GleanPings.spoc, "submit");
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,
recommendation_id: undefined,
tile_id: undefined,
});
assert.notCalled(Glean.pocket.shim.set);
assert.notCalled(GleanPings.spoc.submit);
});
});
describe("#handleAboutSponsoredTopSites", () => {
it("should record a Glean topsites.showPrivacyClick event on action", async () => {
const data = {
position: 42,
advertiser_name: "mozilla",
tile_id: 4567,
};
instance = new TelemetryFeed();
const session_id = "decafc0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.topsites.showPrivacyClick, "record");
await instance.handleAboutSponsoredTopSites({ data });
assert.calledOnce(Glean.topsites.showPrivacyClick.record);
assert.calledWith(Glean.topsites.showPrivacyClick.record, {
advertiser_name: data.advertiser_name,
tile_id: data.tile_id,
newtab_visit_id: session_id,
position: data.position,
});
});
it("should not record a Glean topsites.showPrivacyClick event if there's no session", async () => {
const data = {
position: 42,
advertiser_name: "mozilla",
tile_id: 4567,
};
instance = new TelemetryFeed();
sandbox.stub(instance.sessions, "get").returns(null);
sandbox.spy(Glean.topsites.showPrivacyClick, "record");
await instance.handleAboutSponsoredTopSites({ data });
assert.notCalled(Glean.topsites.showPrivacyClick.record);
});
});
describe("#handleBlockUrl", () => {
it("shouldn't record events for pocket cards' dismisses", async () => {
const data = [
{
// Shouldn't record anything for this one
is_pocket_card: true,
position: 43,
tile_id: undefined,
},
];
instance = new TelemetryFeed();
const session_id = "decafc0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.topsites.dismiss, "record");
await instance.handleBlockUrl({ data });
assert.notCalled(Glean.topsites.dismiss.record);
});
it("should record a Glean topsites.dismiss event on action", async () => {
const data = [
{
is_pocket_card: false,
position: 42,
advertiser_name: "mozilla",
tile_id: 4567,
isSponsoredTopSite: 1, // for some reason this is an int.
},
];
instance = new TelemetryFeed();
const session_id = "decafc0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.topsites.dismiss, "record");
await instance.handleBlockUrl({ data });
assert.calledOnce(Glean.topsites.dismiss.record);
assert.calledWith(Glean.topsites.dismiss.record, {
advertiser_name: data[0].advertiser_name,
tile_id: data[0].tile_id,
newtab_visit_id: session_id,
is_sponsored: !!data[0].isSponsoredTopSite,
position: data[0].position,
});
});
it("should record a Glean topsites.dismiss event on action on non-sponsored topsite", async () => {
const data = [
{
is_pocket_card: false,
position: 42,
tile_id: undefined,
},
];
instance = new TelemetryFeed();
const session_id = "decafc0ffee";
sandbox.stub(instance.sessions, "get").returns({ session_id });
sandbox.spy(Glean.topsites.dismiss, "record");
await instance.handleBlockUrl({ data });
assert.calledOnce(Glean.topsites.dismiss.record);
assert.calledWith(Glean.topsites.dismiss.record, {
advertiser_name: undefined,
tile_id: undefined,
newtab_visit_id: session_id,
is_sponsored: false,
position: data[0].position,
});
});
it("should not record a Glean topsites.dismiss event if there's no session", async () => {
const data = {};
instance = new TelemetryFeed();
sandbox.stub(instance.sessions, "get").returns(null);
sandbox.spy(Glean.topsites.dismiss, "record");
await instance.handleBlockUrl({ data });
assert.notCalled(Glean.topsites.dismiss.record);
});
});
});