mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-23 02:10:12 +01:00
1127 lines
34 KiB
JavaScript
1127 lines
34 KiB
JavaScript
import { strict as assert } from 'assert';
|
|
import sinon from 'sinon';
|
|
import { toHex } from '@metamask/controller-utils';
|
|
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app';
|
|
import { createSegmentMock } from '../lib/segment';
|
|
import {
|
|
METAMETRICS_ANONYMOUS_ID,
|
|
METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
|
MetaMetricsUserTrait,
|
|
} from '../../../shared/constants/metametrics';
|
|
import waitUntilCalled from '../../../test/lib/wait-until-called';
|
|
import {
|
|
CHAIN_IDS,
|
|
CURRENCY_SYMBOLS,
|
|
NETWORK_TYPES,
|
|
} from '../../../shared/constants/network';
|
|
import * as Utils from '../lib/util';
|
|
import MetaMetricsController from './metametrics';
|
|
|
|
const segment = createSegmentMock(2, 10000);
|
|
|
|
const VERSION = '0.0.1-test';
|
|
const FAKE_CHAIN_ID = '0x1338';
|
|
const LOCALE = 'en_US';
|
|
const TEST_META_METRICS_ID = '0xabc';
|
|
const DUMMY_ACTION_ID = 'DUMMY_ACTION_ID';
|
|
const MOCK_EXTENSION_ID = 'testid';
|
|
|
|
const MOCK_EXTENSION = {
|
|
runtime: {
|
|
id: MOCK_EXTENSION_ID,
|
|
setUninstallURL: () => undefined,
|
|
},
|
|
};
|
|
|
|
const MOCK_TRAITS = {
|
|
test_boolean: true,
|
|
test_string: 'abc',
|
|
test_number: 123,
|
|
test_bool_array: [true, true, false],
|
|
test_string_array: ['test', 'test', 'test'],
|
|
test_boolean_array: [1, 2, 3],
|
|
};
|
|
|
|
const MOCK_INVALID_TRAITS = {
|
|
test_null: null,
|
|
test_array_multi_types: [true, 'a', 1],
|
|
};
|
|
|
|
const DEFAULT_TEST_CONTEXT = {
|
|
app: {
|
|
name: 'MetaMask Extension',
|
|
version: VERSION,
|
|
extensionId: MOCK_EXTENSION_ID,
|
|
},
|
|
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
|
referrer: undefined,
|
|
userAgent: window.navigator.userAgent,
|
|
};
|
|
|
|
const DEFAULT_SHARED_PROPERTIES = {
|
|
chain_id: FAKE_CHAIN_ID,
|
|
locale: LOCALE.replace('_', '-'),
|
|
environment_type: 'background',
|
|
};
|
|
|
|
const DEFAULT_EVENT_PROPERTIES = {
|
|
category: 'Unit Test',
|
|
revenue: undefined,
|
|
value: undefined,
|
|
currency: undefined,
|
|
extensionId: MOCK_EXTENSION_ID,
|
|
...DEFAULT_SHARED_PROPERTIES,
|
|
};
|
|
|
|
const DEFAULT_PAGE_PROPERTIES = {
|
|
...DEFAULT_SHARED_PROPERTIES,
|
|
};
|
|
|
|
function getMockNetworkController() {
|
|
let state = {
|
|
providerConfig: {
|
|
type: NETWORK_TYPES.GOERLI,
|
|
chainId: FAKE_CHAIN_ID,
|
|
},
|
|
network: 'loading',
|
|
};
|
|
const onNetworkDidChange = sinon.stub();
|
|
const updateState = (newState) => {
|
|
state = { ...state, ...newState };
|
|
onNetworkDidChange.getCall(0).args[0]();
|
|
};
|
|
return {
|
|
store: {
|
|
getState: () => state,
|
|
updateState,
|
|
},
|
|
onNetworkDidChange,
|
|
};
|
|
}
|
|
|
|
function getMockPreferencesStore({ currentLocale = LOCALE } = {}) {
|
|
let preferencesStore = {
|
|
currentLocale,
|
|
};
|
|
const subscribe = sinon.stub();
|
|
const updateState = (newState) => {
|
|
preferencesStore = { ...preferencesStore, ...newState };
|
|
subscribe.getCall(0).args[0](preferencesStore);
|
|
};
|
|
return {
|
|
getState: sinon.stub().returns(preferencesStore),
|
|
updateState,
|
|
subscribe,
|
|
};
|
|
}
|
|
|
|
const SAMPLE_PERSISTED_EVENT = {
|
|
id: 'testid',
|
|
persist: true,
|
|
category: 'Unit Test',
|
|
successEvent: 'sample persisted event success',
|
|
failureEvent: 'sample persisted event failure',
|
|
properties: {
|
|
test: true,
|
|
},
|
|
};
|
|
|
|
const SAMPLE_NON_PERSISTED_EVENT = {
|
|
id: 'testid2',
|
|
persist: false,
|
|
category: 'Unit Test',
|
|
successEvent: 'sample non-persisted event success',
|
|
failureEvent: 'sample non-persisted event failure',
|
|
uniqueIdentifier: 'sample-non-persisted-event',
|
|
properties: {
|
|
test: true,
|
|
},
|
|
};
|
|
|
|
function getMetaMetricsController({
|
|
participateInMetaMetrics = true,
|
|
metaMetricsId = TEST_META_METRICS_ID,
|
|
preferencesStore = getMockPreferencesStore(),
|
|
networkController = getMockNetworkController(),
|
|
segmentInstance,
|
|
} = {}) {
|
|
return new MetaMetricsController({
|
|
segment: segmentInstance || segment,
|
|
getCurrentChainId: () =>
|
|
networkController.store.getState().providerConfig.chainId,
|
|
onNetworkDidChange:
|
|
networkController.onNetworkDidChange.bind(networkController),
|
|
preferencesStore,
|
|
version: '0.0.1',
|
|
environment: 'test',
|
|
initState: {
|
|
participateInMetaMetrics,
|
|
metaMetricsId,
|
|
fragments: {
|
|
testid: SAMPLE_PERSISTED_EVENT,
|
|
testid2: SAMPLE_NON_PERSISTED_EVENT,
|
|
},
|
|
events: {},
|
|
},
|
|
extension: MOCK_EXTENSION,
|
|
});
|
|
}
|
|
describe('MetaMetricsController', function () {
|
|
const now = new Date();
|
|
let clock;
|
|
beforeEach(function () {
|
|
clock = sinon.useFakeTimers(now.getTime());
|
|
sinon.stub(Utils, 'generateRandomId').returns('DUMMY_RANDOM_ID');
|
|
});
|
|
|
|
describe('constructor', function () {
|
|
it('should properly initialize', function () {
|
|
const mock = sinon.mock(segment);
|
|
mock
|
|
.expects('track')
|
|
.once()
|
|
.withArgs({
|
|
event: 'sample non-persisted event failure',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
test: true,
|
|
},
|
|
messageId: 'sample-non-persisted-event-failure',
|
|
timestamp: new Date(),
|
|
});
|
|
const metaMetricsController = getMetaMetricsController();
|
|
assert.strictEqual(metaMetricsController.version, VERSION);
|
|
assert.strictEqual(metaMetricsController.chainId, FAKE_CHAIN_ID);
|
|
assert.strictEqual(
|
|
metaMetricsController.state.participateInMetaMetrics,
|
|
true,
|
|
);
|
|
assert.strictEqual(
|
|
metaMetricsController.state.metaMetricsId,
|
|
TEST_META_METRICS_ID,
|
|
);
|
|
assert.strictEqual(
|
|
metaMetricsController.locale,
|
|
LOCALE.replace('_', '-'),
|
|
);
|
|
assert.deepStrictEqual(metaMetricsController.state.fragments, {
|
|
testid: SAMPLE_PERSISTED_EVENT,
|
|
});
|
|
mock.verify();
|
|
});
|
|
|
|
it('should update when network changes', function () {
|
|
const networkController = getMockNetworkController();
|
|
const metaMetricsController = getMetaMetricsController({
|
|
networkController,
|
|
});
|
|
networkController.store.updateState({
|
|
providerConfig: {
|
|
type: 'NEW_NETWORK',
|
|
chainId: '0xaab',
|
|
},
|
|
});
|
|
assert.strictEqual(metaMetricsController.chainId, '0xaab');
|
|
});
|
|
|
|
it('should update when preferences changes', function () {
|
|
const preferencesStore = getMockPreferencesStore();
|
|
const metaMetricsController = getMetaMetricsController({
|
|
preferencesStore,
|
|
});
|
|
preferencesStore.updateState({
|
|
currentLocale: 'en_UK',
|
|
});
|
|
assert.strictEqual(metaMetricsController.locale, 'en-UK');
|
|
});
|
|
});
|
|
|
|
describe('generateMetaMetricsId', function () {
|
|
it('should generate an 0x prefixed hex string', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
assert.equal(
|
|
metaMetricsController.generateMetaMetricsId().startsWith('0x'),
|
|
true,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('identify', function () {
|
|
it('should call segment.identify for valid traits if user is participating in metametrics', async function () {
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: true,
|
|
metaMetricsId: TEST_META_METRICS_ID,
|
|
});
|
|
const mock = sinon.mock(segment);
|
|
|
|
mock.expects('identify').once().withArgs({
|
|
userId: TEST_META_METRICS_ID,
|
|
traits: MOCK_TRAITS,
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
|
|
metaMetricsController.identify({
|
|
...MOCK_TRAITS,
|
|
...MOCK_INVALID_TRAITS,
|
|
});
|
|
|
|
mock.verify();
|
|
});
|
|
|
|
it('should transform date type traits into ISO-8601 timestamp strings', async function () {
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: true,
|
|
metaMetricsId: TEST_META_METRICS_ID,
|
|
});
|
|
const mock = sinon.mock(segment);
|
|
|
|
const mockDate = new Date();
|
|
const mockDateISOString = mockDate.toISOString();
|
|
|
|
mock
|
|
.expects('identify')
|
|
.once()
|
|
.withArgs({
|
|
userId: TEST_META_METRICS_ID,
|
|
traits: {
|
|
test_date: mockDateISOString,
|
|
},
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
|
|
metaMetricsController.identify({
|
|
test_date: mockDate,
|
|
});
|
|
mock.verify();
|
|
});
|
|
|
|
it('should not call segment.identify if user is not participating in metametrics', function () {
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: false,
|
|
});
|
|
const mock = sinon.mock(segment);
|
|
|
|
mock.expects('identify').never();
|
|
|
|
metaMetricsController.identify(MOCK_TRAITS);
|
|
mock.verify();
|
|
});
|
|
|
|
it('should not call segment.identify if there are no valid traits to identify', async function () {
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: true,
|
|
metaMetricsId: TEST_META_METRICS_ID,
|
|
});
|
|
const mock = sinon.mock(segment);
|
|
|
|
mock.expects('identify').never();
|
|
|
|
metaMetricsController.identify(MOCK_INVALID_TRAITS);
|
|
mock.verify();
|
|
});
|
|
});
|
|
|
|
describe('setParticipateInMetaMetrics', function () {
|
|
it('should update the value of participateInMetaMetrics', function () {
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: null,
|
|
metaMetricsId: null,
|
|
});
|
|
assert.equal(metaMetricsController.state.participateInMetaMetrics, null);
|
|
metaMetricsController.setParticipateInMetaMetrics(true);
|
|
assert.equal(metaMetricsController.state.participateInMetaMetrics, true);
|
|
metaMetricsController.setParticipateInMetaMetrics(false);
|
|
assert.equal(metaMetricsController.state.participateInMetaMetrics, false);
|
|
});
|
|
it('should generate and update the metaMetricsId when set to true', function () {
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: null,
|
|
metaMetricsId: null,
|
|
});
|
|
assert.equal(metaMetricsController.state.metaMetricsId, null);
|
|
metaMetricsController.setParticipateInMetaMetrics(true);
|
|
assert.equal(typeof metaMetricsController.state.metaMetricsId, 'string');
|
|
});
|
|
it('should nullify the metaMetricsId when set to false', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
metaMetricsController.setParticipateInMetaMetrics(false);
|
|
assert.equal(metaMetricsController.state.metaMetricsId, null);
|
|
});
|
|
});
|
|
|
|
describe('submitEvent', function () {
|
|
it('should not track an event if user is not participating in metametrics', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: false,
|
|
});
|
|
mock.expects('track').never();
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: {
|
|
test: 1,
|
|
},
|
|
});
|
|
mock.verify();
|
|
});
|
|
|
|
it('should track an event if user has not opted in, but isOptIn is true', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: true,
|
|
});
|
|
mock
|
|
.expects('track')
|
|
.once()
|
|
.withArgs({
|
|
event: 'Fake Event',
|
|
anonymousId: METAMETRICS_ANONYMOUS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
test: 1,
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
metaMetricsController.submitEvent(
|
|
{
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: {
|
|
test: 1,
|
|
},
|
|
},
|
|
{ isOptIn: true },
|
|
);
|
|
mock.verify();
|
|
});
|
|
|
|
it('should track an event during optin and allow for metaMetricsId override', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: true,
|
|
});
|
|
mock
|
|
.expects('track')
|
|
.once()
|
|
.withArgs({
|
|
event: 'Fake Event',
|
|
userId: 'TESTID',
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
test: 1,
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
metaMetricsController.submitEvent(
|
|
{
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: {
|
|
test: 1,
|
|
},
|
|
},
|
|
{ isOptIn: true, metaMetricsId: 'TESTID' },
|
|
);
|
|
mock.verify();
|
|
});
|
|
|
|
it('should track a legacy event', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController();
|
|
mock
|
|
.expects('track')
|
|
.once()
|
|
.withArgs({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
test: 1,
|
|
legacy_event: true,
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
metaMetricsController.submitEvent(
|
|
{
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: {
|
|
test: 1,
|
|
},
|
|
},
|
|
{ matomoEvent: true },
|
|
);
|
|
mock.verify();
|
|
});
|
|
|
|
it('should track a non legacy event', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController();
|
|
mock
|
|
.expects('track')
|
|
.once()
|
|
.withArgs({
|
|
event: 'Fake Event',
|
|
properties: {
|
|
test: 1,
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
userId: TEST_META_METRICS_ID,
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: {
|
|
test: 1,
|
|
},
|
|
});
|
|
mock.verify();
|
|
});
|
|
|
|
it('should immediately flush queue if flushImmediately set to true', async function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const flushStub = sinon.stub(segment, 'flush');
|
|
const flushCalled = waitUntilCalled(flushStub, segment);
|
|
metaMetricsController.submitEvent(
|
|
{
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
},
|
|
{ flushImmediately: true },
|
|
);
|
|
assert.doesNotReject(flushCalled());
|
|
});
|
|
|
|
it('should throw if event or category not provided', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
assert.rejects(
|
|
() => metaMetricsController.submitEvent({ event: 'test' }),
|
|
/Must specify event and category\./u,
|
|
'must specify category',
|
|
);
|
|
|
|
assert.rejects(
|
|
() => metaMetricsController.submitEvent({ category: 'test' }),
|
|
/Must specify event and category\./u,
|
|
'must specify event',
|
|
);
|
|
});
|
|
|
|
it('should throw if provided sensitiveProperties, when excludeMetaMetricsId is true', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
assert.rejects(
|
|
() =>
|
|
metaMetricsController.submitEvent(
|
|
{
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
sensitiveProperties: { foo: 'bar' },
|
|
},
|
|
{ excludeMetaMetricsId: true },
|
|
),
|
|
/sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag/u,
|
|
);
|
|
});
|
|
|
|
it('should track sensitiveProperties in a separate, anonymous event', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const spy = sinon.spy(segment, 'track');
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
sensitiveProperties: { foo: 'bar' },
|
|
});
|
|
assert.ok(spy.calledTwice);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
anonymousId: METAMETRICS_ANONYMOUS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
foo: 'bar',
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: DEFAULT_EVENT_PROPERTIES,
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('trackPage', function () {
|
|
it('should track a page view', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController();
|
|
mock
|
|
.expects('page')
|
|
.once()
|
|
.withArgs({
|
|
name: 'home',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
params: null,
|
|
...DEFAULT_PAGE_PROPERTIES,
|
|
},
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
metaMetricsController.trackPage({
|
|
name: 'home',
|
|
params: null,
|
|
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
|
|
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
|
});
|
|
mock.verify();
|
|
});
|
|
|
|
it('should not track a page view if user is not participating in metametrics', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController({
|
|
participateInMetaMetrics: false,
|
|
});
|
|
mock.expects('page').never();
|
|
metaMetricsController.trackPage({
|
|
name: 'home',
|
|
params: null,
|
|
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
|
|
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
|
});
|
|
mock.verify();
|
|
});
|
|
|
|
it('should track a page view if isOptInPath is true and user not yet opted in', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController({
|
|
preferencesStore: getMockPreferencesStore({
|
|
participateInMetaMetrics: null,
|
|
}),
|
|
});
|
|
mock
|
|
.expects('page')
|
|
.once()
|
|
.withArgs({
|
|
name: 'home',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
params: null,
|
|
...DEFAULT_PAGE_PROPERTIES,
|
|
},
|
|
messageId: Utils.generateRandomId(),
|
|
timestamp: new Date(),
|
|
});
|
|
metaMetricsController.trackPage(
|
|
{
|
|
name: 'home',
|
|
params: null,
|
|
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
|
|
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
|
},
|
|
{ isOptInPath: true },
|
|
);
|
|
mock.verify();
|
|
});
|
|
|
|
it('multiple trackPage call with same actionId should result in same messageId being sent to segment', function () {
|
|
const mock = sinon.mock(segment);
|
|
const metaMetricsController = getMetaMetricsController({
|
|
preferencesStore: getMockPreferencesStore({
|
|
participateInMetaMetrics: null,
|
|
}),
|
|
});
|
|
mock
|
|
.expects('page')
|
|
.twice()
|
|
.withArgs({
|
|
name: 'home',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
params: null,
|
|
...DEFAULT_PAGE_PROPERTIES,
|
|
},
|
|
messageId: DUMMY_ACTION_ID,
|
|
timestamp: new Date(),
|
|
});
|
|
metaMetricsController.trackPage(
|
|
{
|
|
name: 'home',
|
|
params: null,
|
|
actionId: DUMMY_ACTION_ID,
|
|
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
|
|
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
|
},
|
|
{ isOptInPath: true },
|
|
);
|
|
metaMetricsController.trackPage(
|
|
{
|
|
name: 'home',
|
|
params: null,
|
|
actionId: DUMMY_ACTION_ID,
|
|
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
|
|
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
|
|
},
|
|
{ isOptInPath: true },
|
|
);
|
|
mock.verify();
|
|
});
|
|
});
|
|
|
|
describe('deterministic messageId', function () {
|
|
it('should use the actionId as messageId when provided', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const spy = sinon.spy(segment, 'track');
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: { foo: 'bar' },
|
|
actionId: '0x001',
|
|
});
|
|
assert.ok(spy.calledOnce);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
foo: 'bar',
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: '0x001',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should append 0x000 to the actionId of anonymized event when tracking sensitiveProperties', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const spy = sinon.spy(segment, 'track');
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
sensitiveProperties: { foo: 'bar' },
|
|
actionId: '0x001',
|
|
});
|
|
assert.ok(spy.calledTwice);
|
|
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
anonymousId: METAMETRICS_ANONYMOUS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
foo: 'bar',
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: '0x001-0x000',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: '0x001',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should use the uniqueIdentifier as messageId when provided', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const spy = sinon.spy(segment, 'track');
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: { foo: 'bar' },
|
|
uniqueIdentifier: 'transaction-submitted-0000',
|
|
});
|
|
assert.ok(spy.calledOnce);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
foo: 'bar',
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: 'transaction-submitted-0000',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should append 0x000 to the uniqueIdentifier of anonymized event when tracking sensitiveProperties', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const spy = sinon.spy(segment, 'track');
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
sensitiveProperties: { foo: 'bar' },
|
|
uniqueIdentifier: 'transaction-submitted-0000',
|
|
});
|
|
assert.ok(spy.calledTwice);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
anonymousId: METAMETRICS_ANONYMOUS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
foo: 'bar',
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: 'transaction-submitted-0000-0x000',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: 'transaction-submitted-0000',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should combine the uniqueIdentifier and actionId as messageId when both provided', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const spy = sinon.spy(segment, 'track');
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
properties: { foo: 'bar' },
|
|
actionId: '0x001',
|
|
uniqueIdentifier: 'transaction-submitted-0000',
|
|
});
|
|
assert.ok(spy.calledOnce);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
foo: 'bar',
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: 'transaction-submitted-0000-0x001',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should append 0x000 to the combined uniqueIdentifier and actionId of anonymized event when tracking sensitiveProperties', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const spy = sinon.spy(segment, 'track');
|
|
metaMetricsController.submitEvent({
|
|
event: 'Fake Event',
|
|
category: 'Unit Test',
|
|
sensitiveProperties: { foo: 'bar' },
|
|
actionId: '0x001',
|
|
uniqueIdentifier: 'transaction-submitted-0000',
|
|
});
|
|
assert.ok(spy.calledTwice);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
anonymousId: METAMETRICS_ANONYMOUS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
foo: 'bar',
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: 'transaction-submitted-0000-0x001-0x000',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
assert.ok(
|
|
spy.calledWith({
|
|
event: 'Fake Event',
|
|
userId: TEST_META_METRICS_ID,
|
|
context: DEFAULT_TEST_CONTEXT,
|
|
properties: {
|
|
...DEFAULT_EVENT_PROPERTIES,
|
|
},
|
|
messageId: 'transaction-submitted-0000-0x001',
|
|
timestamp: new Date(),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('_buildUserTraitsObject', function () {
|
|
it('should return full user traits object on first call', function () {
|
|
const MOCK_ALL_TOKENS = {
|
|
[toHex(1)]: {
|
|
'0x1235ce91d74254f29d4609f25932fe6d97bf4842': [
|
|
{
|
|
address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91',
|
|
},
|
|
{
|
|
address: '0xabc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9',
|
|
},
|
|
],
|
|
'0xe364b0f9d1879e53e8183055c9d7dd2b7375d86b': [
|
|
{
|
|
address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91',
|
|
},
|
|
],
|
|
},
|
|
[toHex(4)]: {
|
|
'0x1235ce91d74254f29d4609f25932fe6d97bf4842': [
|
|
{
|
|
address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91',
|
|
},
|
|
{
|
|
address: '0x12317F958D2ee523a2206206994597C13D831ec7',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const metaMetricsController = getMetaMetricsController();
|
|
const traits = metaMetricsController._buildUserTraitsObject({
|
|
addressBook: {
|
|
[CHAIN_IDS.MAINNET]: [{ address: '0x' }],
|
|
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
|
},
|
|
allNfts: {
|
|
'0xac706cE8A9BF27Afecf080fB298d0ee13cfb978A': {
|
|
[toHex(56)]: [
|
|
{
|
|
address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91',
|
|
tokenId: '100',
|
|
},
|
|
{
|
|
address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91',
|
|
tokenId: '101',
|
|
},
|
|
{
|
|
address: '0x7488d2ce5deb26db021285b50b661d655eb3d3d9',
|
|
tokenId: '99',
|
|
},
|
|
],
|
|
},
|
|
'0xe04AB39684A24D8D4124b114F3bd6FBEB779cacA': {
|
|
[toHex(59)]: [
|
|
{
|
|
address: '0x63d646bc7380562376d5de205123a57b1718184d',
|
|
tokenId: '14',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
allTokens: MOCK_ALL_TOKENS,
|
|
networkConfigurations: {
|
|
'network-configuration-id-1': {
|
|
chainId: CHAIN_IDS.MAINNET,
|
|
ticker: CURRENCY_SYMBOLS.ETH,
|
|
},
|
|
'network-configuration-id-2': {
|
|
chainId: CHAIN_IDS.GOERLI,
|
|
ticker: CURRENCY_SYMBOLS.TEST_ETH,
|
|
},
|
|
'network-configuration-id-3': { chainId: '0xaf' },
|
|
},
|
|
identities: [{}, {}],
|
|
ledgerTransportType: 'web-hid',
|
|
openSeaEnabled: true,
|
|
useNftDetection: false,
|
|
theme: 'default',
|
|
useTokenDetection: true,
|
|
desktopEnabled: false,
|
|
security_providers: [],
|
|
});
|
|
|
|
assert.deepEqual(traits, {
|
|
[MetaMetricsUserTrait.AddressBookEntries]: 3,
|
|
[MetaMetricsUserTrait.InstallDateExt]: '',
|
|
[MetaMetricsUserTrait.LedgerConnectionType]: 'web-hid',
|
|
[MetaMetricsUserTrait.NetworksAdded]: [
|
|
CHAIN_IDS.MAINNET,
|
|
CHAIN_IDS.GOERLI,
|
|
'0xaf',
|
|
],
|
|
[MetaMetricsUserTrait.NetworksWithoutTicker]: ['0xaf'],
|
|
[MetaMetricsUserTrait.NftAutodetectionEnabled]: false,
|
|
[MetaMetricsUserTrait.NumberOfAccounts]: 2,
|
|
[MetaMetricsUserTrait.NumberOfNftCollections]: 3,
|
|
[MetaMetricsUserTrait.NumberOfNfts]: 4,
|
|
[MetaMetricsUserTrait.NumberOfTokens]: 5,
|
|
[MetaMetricsUserTrait.OpenseaApiEnabled]: true,
|
|
[MetaMetricsUserTrait.ThreeBoxEnabled]: false,
|
|
[MetaMetricsUserTrait.Theme]: 'default',
|
|
[MetaMetricsUserTrait.TokenDetectionEnabled]: true,
|
|
[MetaMetricsUserTrait.DesktopEnabled]: false,
|
|
[MetaMetricsUserTrait.SecurityProviders]: [],
|
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
|
[MetaMetricsUserTrait.MmiExtensionId]: 'testid',
|
|
[MetaMetricsUserTrait.MmiAccountAddress]: null,
|
|
[MetaMetricsUserTrait.MmiIsCustodian]: false,
|
|
///: END:ONLY_INCLUDE_IN
|
|
});
|
|
});
|
|
|
|
it('should return only changed traits object on subsequent calls', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
metaMetricsController._buildUserTraitsObject({
|
|
addressBook: {
|
|
[CHAIN_IDS.MAINNET]: [{ address: '0x' }],
|
|
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
|
},
|
|
allTokens: {},
|
|
networkConfigurations: {
|
|
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
|
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
|
},
|
|
ledgerTransportType: 'web-hid',
|
|
openSeaEnabled: true,
|
|
identities: [{}, {}],
|
|
useNftDetection: false,
|
|
theme: 'default',
|
|
useTokenDetection: true,
|
|
desktopEnabled: false,
|
|
});
|
|
|
|
const updatedTraits = metaMetricsController._buildUserTraitsObject({
|
|
addressBook: {
|
|
[CHAIN_IDS.MAINNET]: [{ address: '0x' }, { address: '0x1' }],
|
|
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
|
},
|
|
allTokens: {
|
|
[toHex(1)]: {
|
|
'0xabcde': [{ '0x12345': { address: '0xtestAddress' } }],
|
|
},
|
|
},
|
|
networkConfigurations: {
|
|
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
|
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
|
},
|
|
ledgerTransportType: 'web-hid',
|
|
openSeaEnabled: false,
|
|
identities: [{}, {}, {}],
|
|
useNftDetection: false,
|
|
theme: 'default',
|
|
useTokenDetection: true,
|
|
desktopEnabled: false,
|
|
});
|
|
|
|
assert.deepEqual(updatedTraits, {
|
|
[MetaMetricsUserTrait.AddressBookEntries]: 4,
|
|
[MetaMetricsUserTrait.NumberOfAccounts]: 3,
|
|
[MetaMetricsUserTrait.NumberOfTokens]: 1,
|
|
[MetaMetricsUserTrait.OpenseaApiEnabled]: false,
|
|
});
|
|
});
|
|
|
|
it('should return null if no traits changed', function () {
|
|
const metaMetricsController = getMetaMetricsController();
|
|
metaMetricsController._buildUserTraitsObject({
|
|
addressBook: {
|
|
[CHAIN_IDS.MAINNET]: [{ address: '0x' }],
|
|
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
|
},
|
|
allTokens: {},
|
|
networkConfigurations: {
|
|
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
|
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
|
},
|
|
ledgerTransportType: 'web-hid',
|
|
openSeaEnabled: true,
|
|
identities: [{}, {}],
|
|
useNftDetection: true,
|
|
theme: 'default',
|
|
useTokenDetection: true,
|
|
desktopEnabled: false,
|
|
});
|
|
|
|
const updatedTraits = metaMetricsController._buildUserTraitsObject({
|
|
addressBook: {
|
|
[CHAIN_IDS.MAINNET]: [{ address: '0x' }],
|
|
[CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }],
|
|
},
|
|
allTokens: {},
|
|
networkConfigurations: {
|
|
'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET },
|
|
'network-configuration-id-2': { chainId: CHAIN_IDS.GOERLI },
|
|
},
|
|
ledgerTransportType: 'web-hid',
|
|
openSeaEnabled: true,
|
|
identities: [{}, {}],
|
|
useNftDetection: true,
|
|
theme: 'default',
|
|
useTokenDetection: true,
|
|
desktopEnabled: false,
|
|
});
|
|
|
|
assert.equal(updatedTraits, null);
|
|
});
|
|
});
|
|
|
|
describe('submitting segmentApiCalls to segment SDK', function () {
|
|
it('should add event to store when submitting to SDK', function () {
|
|
const metaMetricsController = getMetaMetricsController({});
|
|
metaMetricsController.trackPage({}, { isOptIn: true });
|
|
const { segmentApiCalls } = metaMetricsController.store.getState();
|
|
assert(Object.keys(segmentApiCalls).length > 0);
|
|
});
|
|
|
|
it('should remove event from store when callback is invoked', function () {
|
|
const segmentInstance = createSegmentMock(2, 10000);
|
|
const stubFn = (_, cb) => {
|
|
cb();
|
|
};
|
|
sinon.stub(segmentInstance, 'track').callsFake(stubFn);
|
|
sinon.stub(segmentInstance, 'page').callsFake(stubFn);
|
|
|
|
const metaMetricsController = getMetaMetricsController({
|
|
segmentInstance,
|
|
});
|
|
metaMetricsController.trackPage({}, { isOptIn: true });
|
|
const { segmentApiCalls } = metaMetricsController.store.getState();
|
|
assert(Object.keys(segmentApiCalls).length === 0);
|
|
});
|
|
});
|
|
|
|
afterEach(function () {
|
|
// flush the queues manually after each test
|
|
segment.flush();
|
|
clock.restore();
|
|
sinon.restore();
|
|
});
|
|
});
|