Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"use strict";
ChromeUtils.defineESModuleGetters(this, {
NewTabAttributionService:
});
const { HttpServer } = ChromeUtils.importESModule(
);
const BinaryInputStream = Components.Constructor(
"@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream"
);
const PREF_LEADER = "toolkit.telemetry.dap.leader.url";
const PREF_HELPER = "toolkit.telemetry.dap.helper.url";
const TASK_ID = "DSZGMFh26hBYXNaKvhL_N4AHA3P5lDn19on1vFPBxJM";
const MAX_CONVERSIONS = 5;
const DAY_IN_MILLI = 1000 * 60 * 60 * 24;
const LOOKBACK_DAYS = 1;
const MAX_LOOKBACK_DAYS = 30;
const HISTOGRAM_SIZE = 5;
class MockDateProvider {
constructor() {
this._now = Date.now();
}
now() {
return this._now;
}
add(interval_ms) {
this._now += interval_ms;
}
}
class MockDAPTelemetrySender {
constructor() {
this.receivedMeasurements = [];
}
async sendDAPMeasurement(task, measurement, _options) {
this.receivedMeasurements.push({
task,
measurement,
});
}
}
class MockServer {
constructor() {
this.receivedReports = [];
const server = new HttpServer();
server.registerPrefixHandler(
"/leader_endpoint/tasks/",
this.uploadHandler.bind(this)
);
this._server = server;
}
start() {
this._server.start(-1);
this.orig_leader = Services.prefs.getStringPref(PREF_LEADER);
this.orig_helper = Services.prefs.getStringPref(PREF_HELPER);
const i = this._server.identity;
const serverAddr = `${i.primaryScheme}://${i.primaryHost}:${i.primaryPort}`;
Services.prefs.setStringPref(PREF_LEADER, `${serverAddr}/leader_endpoint`);
Services.prefs.setStringPref(PREF_HELPER, `${serverAddr}/helper_endpoint`);
}
async stop() {
Services.prefs.setStringPref(PREF_LEADER, this.orig_leader);
Services.prefs.setStringPref(PREF_HELPER, this.orig_helper);
await this._server.stop();
}
uploadHandler(request, response) {
let body = new BinaryInputStream(request.bodyInputStream);
this.receivedReports.push({
contentType: request.getHeader("Content-Type"),
size: body.available(),
});
response.setStatusLine(request.httpVersion, 200);
}
}
add_setup(async function () {
do_get_profile();
});
add_task(async function testSuccessfulConversion() {
const mockSender = new MockDAPTelemetrySender();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
});
const partnerIdentifier = "partner_identifier";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: conversionSettings,
});
await privateAttribution.onAttributionEvent("click", {
partner_id: partnerIdentifier,
conversion: conversionSettings,
});
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
const expectedMeasurement = {
task: {
id: conversionSettings.task_id,
vdaf: conversionSettings.vdaf,
bits: conversionSettings.bits,
length: conversionSettings.length,
time_precision: conversionSettings.time_precision,
},
measurement: conversionSettings.index,
};
const receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(receivedMeasurement, expectedMeasurement);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testConversionWithoutImpression() {
const mockSender = new MockDAPTelemetrySender();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
});
const partnerIdentifier = "partner_identifier_no_impression";
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testConversionWithInvalidLookbackDays() {
const mockSender = new MockDAPTelemetrySender();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
});
const partnerIdentifier = "partner_identifier";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: conversionSettings,
});
await privateAttribution.onAttributionConversion(
partnerIdentifier,
MAX_LOOKBACK_DAYS + 1,
"view"
);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testSelectionByLastView() {
const mockSender = new MockDAPTelemetrySender();
const mockDateProvider = new MockDateProvider();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
dateProvider: mockDateProvider,
});
const partnerIdentifier = "partner_identifier_last_view";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
const selectedViewIndex = 1;
const ignoredViewIndex = 2;
const clickIndex = 3;
// View event that will be ignored, as a more recent view will exist
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: ignoredViewIndex,
},
});
// step forward time
mockDateProvider.add(10);
// View event that will be selected, as no more recent view exists
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: selectedViewIndex,
},
});
// step forward time
mockDateProvider.add(10);
// Click event that will be ignored because the match type is "view"
await privateAttribution.onAttributionEvent("click", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: clickIndex,
},
});
// Conversion filtering for "view" finds the view event
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
let receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(receivedMeasurement.measurement, selectedViewIndex);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testSelectionByLastClick() {
const mockSender = new MockDAPTelemetrySender();
const mockDateProvider = new MockDateProvider();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
dateProvider: mockDateProvider,
});
const partnerIdentifier = "partner_identifier_last_click";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
const viewIndex = 1;
const ignoredClickIndex = 2;
const selectedClickIndex = 3;
// Click event that will be ignored, as a more recent click will exist
await privateAttribution.onAttributionEvent("click", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: ignoredClickIndex,
},
});
// step forward time
mockDateProvider.add(10);
// Click event that will be selected, as no more recent click exists
await privateAttribution.onAttributionEvent("click", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: selectedClickIndex,
},
});
// step forward time
mockDateProvider.add(10);
// View event that will be ignored because the match type is "click"
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: viewIndex,
},
});
// Conversion filtering for "click" finds the click event
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"click"
);
let receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(receivedMeasurement.measurement, selectedClickIndex);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testSelectionByLastTouch() {
const mockSender = new MockDAPTelemetrySender();
const mockDateProvider = new MockDateProvider();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
dateProvider: mockDateProvider,
});
const partnerIdentifier = "partner_identifier_last_touch";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
const viewIndex = 1;
const clickIndex = 2;
// Click at clickIndex
await privateAttribution.onAttributionEvent("click", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: clickIndex,
},
});
// step forward time so the view event occurs most recently
mockDateProvider.add(10);
// View at viewIndex
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: {
...conversionSettings,
index: viewIndex,
},
});
// Conversion filtering for "default" finds the view event
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"default"
);
let receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(receivedMeasurement.measurement, viewIndex);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testSelectionByPartnerId() {
const mockSender = new MockDAPTelemetrySender();
const mockDateProvider = new MockDateProvider();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
dateProvider: mockDateProvider,
});
const partnerIdentifier1 = "partner_identifier_1";
const partnerIdentifier2 = "partner_identifier_2";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
const partner1Index = 1;
const partner2Index = 2;
// view event associated with partner 1
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier1,
conversion: {
...conversionSettings,
index: partner1Index,
},
});
// step forward time so the partner 2 event occurs most recently
mockDateProvider.add(10);
// view event associated with partner 2
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier2,
conversion: {
...conversionSettings,
index: partner2Index,
},
});
// Conversion filtering for "default" finds the correct view event
await privateAttribution.onAttributionConversion(
partnerIdentifier1,
LOOKBACK_DAYS,
"default"
);
let receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(receivedMeasurement.measurement, partner1Index);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testExpiredImpressions() {
const mockSender = new MockDAPTelemetrySender();
const mockDateProvider = new MockDateProvider();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
dateProvider: mockDateProvider,
});
const partnerIdentifier = "partner_identifier";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
// Register impression
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: conversionSettings,
});
// Fast-forward time by LOOKBACK_DAYS days + 1 ms
mockDateProvider.add(LOOKBACK_DAYS * DAY_IN_MILLI + 1);
// Conversion doesn't match expired impression
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testConversionBudget() {
const mockSender = new MockDAPTelemetrySender();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
});
const partnerIdentifier = "partner_identifier_budget";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: conversionSettings,
});
// Measurements uploaded for conversions up to MAX_CONVERSIONS
for (let i = 0; i < MAX_CONVERSIONS; i++) {
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
const receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(receivedMeasurement.measurement, conversionSettings.index);
Assert.equal(mockSender.receivedMeasurements.length, 0);
}
// default report uploaded on subsequent conversions
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
const receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(
receivedMeasurement.measurement,
conversionSettings.default_measurement
);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testHistogramSize() {
const mockSender = new MockDAPTelemetrySender();
const privateAttribution = new NewTabAttributionService({
dapTelemetrySender: mockSender,
});
const partnerIdentifier = "partner_identifier_bad_settings";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
// Zero-based index equal to histogram size is out of bounds
index: HISTOGRAM_SIZE,
};
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: conversionSettings,
});
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
const receivedMeasurement = mockSender.receivedMeasurements.pop();
Assert.deepEqual(
receivedMeasurement.measurement,
conversionSettings.default_measurement
);
Assert.equal(mockSender.receivedMeasurements.length, 0);
});
add_task(async function testWithRealDAPSender() {
// Omit mocking DAP telemetry sender in this test to defend against mock
// sender getting out of sync
const mockServer = new MockServer();
mockServer.start();
const privateAttribution = new NewTabAttributionService();
const partnerIdentifier = "partner_identifier_real_dap";
const conversionSettings = {
task_id: TASK_ID,
vdaf: "histogram",
bits: 1,
length: HISTOGRAM_SIZE,
time_precision: 60,
default_measurement: 0,
index: 1,
};
await privateAttribution.onAttributionEvent("view", {
partner_id: partnerIdentifier,
conversion: conversionSettings,
});
await privateAttribution.onAttributionConversion(
partnerIdentifier,
LOOKBACK_DAYS,
"view"
);
await mockServer.stop();
Assert.equal(mockServer.receivedReports.length, 1);
const expectedReport = {
contentType: "application/dap-report",
size: 502,
};
const receivedReport = mockServer.receivedReports.pop();
Assert.deepEqual(receivedReport, expectedReport);
});