fune/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js

1943 lines
65 KiB
JavaScript

/* global Services */
import {
actionCreators as ac,
actionTypes as at,
actionUtils as au,
} from "common/Actions.jsm";
import {
ASRouterEventPing,
BasePing,
ImpressionStatsPing,
SessionPing,
UserEventPing,
} from "test/schemas/pings";
import { FakePrefs, 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.jsm";
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() {}
}
const {
TelemetryFeed,
USER_PREFS_ENCODING,
PREF_IMPRESSION_ID,
TELEMETRY_PREF,
EVENTS_TELEMETRY_PREF,
STRUCTURED_INGESTION_ENDPOINT_PREF,
} = injector({
"lib/UTEventReporting.jsm": { 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.Cu, "reportError");
globals.set("AboutNewTab", {
newTabURLOverridden: false,
newTabURL: "",
});
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();
FakePrefs.prototype.prefs = {};
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", () => {
FakePrefs.prototype.prefs = {};
FakePrefs.prototype.prefs[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(() => {
FakePrefs.prototype.prefs = {};
FakePrefs.prototype.prefs[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(() => {
FakePrefs.prototype.prefs = {};
FakePrefs.prototype.prefs[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 a scalar for deletion-request", () => {
sandbox.spy(Services.telemetry, "scalarSet");
instance.init();
assert.calledOnce(Services.telemetry.scalarSet);
assert.calledWith(
Services.telemetry.scalarSet,
"deletion.request.impression_id",
instance._impressionId
);
});
});
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", () => {
FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
FakePrefs.prototype.prefs[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(() => {
FakePrefs.prototype.prefs = {};
for (const pref of Object.keys(USER_PREFS_ENCODING)) {
FakePrefs.prototype.prefs[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");
});
});
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("#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.Cu.reportError);
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 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", () => {
FakePrefs.prototype.prefs.telemetry = 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", () => {
FakePrefs.prototype.prefs.telemetry = 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 () => {
FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
FakePrefs.prototype.prefs[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 () => {
FakePrefs.prototype.prefs[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);
});
});
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(() => {
FakePrefs.prototype.prefs = {};
});
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", () => {
FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
FakePrefs.prototype.prefs[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);
});
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`, () => {
FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
FakePrefs.prototype.prefs[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 .handleTopSitesImpressionStats on a TOP_SITES_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_IMPRESSION_STATS, data };
sandbox.spy(instance, "handleTopSitesImpressionStats");
instance.onAction(ac.AlsoToMain(action));
assert.calledOnce(instance.handleTopSitesImpressionStats);
assert.deepEqual(
instance.handleTopSitesImpressionStats.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]};
});
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);
});
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);
});
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);
});
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);
});
});
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 }],
});
assert.equal(Object.keys(session.impressionSets).length, 1);
assert.deepEqual(session.impressionSets.foo, [{ id: 1, pos: 0 }]);
// Add another ping with the same source
instance.handleDiscoveryStreamImpressionStats("new_session", {
source: "foo",
tiles: [{ id: 2, pos: 1 }],
});
assert.deepEqual(session.impressionSets.foo, [
{ id: 1, pos: 0 },
{ id: 2, pos: 1 },
]);
// Add another ping with a different source
instance.handleDiscoveryStreamImpressionStats("new_session", {
source: "bar",
tiles: [{ id: 3, pos: 2 }],
});
assert.equal(Object.keys(session.impressionSets).length, 2);
assert.deepEqual(session.impressionSets.foo, [
{ id: 1, pos: 0 },
{ id: 2, pos: 1 },
]);
assert.deepEqual(session.impressionSets.bar, [{ id: 3, pos: 2 }]);
});
});
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);
FakePrefs.prototype.prefs = {};
FakePrefs.prototype.prefs[
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 reportError 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.Cu.reportError);
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("#handleTopSitesImpressionStats", () => {
it("should call sendStructuredIngestionEvent on an impression event", async () => {
const data = {
type: "impression",
tile_id: 42,
source: "newtab",
position: 1,
reporting_url: "https://test.reporting.net/",
};
instance = new TelemetryFeed();
sandbox.spy(instance, "sendStructuredIngestionEvent");
sandbox.spy(Services.telemetry, "keyedScalarAdd");
await instance.handleTopSitesImpressionStats({ 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: 1,
reporting_url: "https://test.reporting.net/",
};
instance = new TelemetryFeed();
sandbox.spy(instance, "sendStructuredIngestionEvent");
sandbox.spy(Services.telemetry, "keyedScalarAdd");
await instance.handleTopSitesImpressionStats({ 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 reportError on unknown pingTypes", async () => {
const data = { type: "unknown_type" };
instance = new TelemetryFeed();
sandbox.spy(instance, "sendStructuredIngestionEvent");
await instance.handleTopSitesImpressionStats({ data });
assert.calledOnce(global.Cu.reportError);
assert.notCalled(instance.sendStructuredIngestionEvent);
});
});
});