1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-27 12:56:01 +01:00
metamask-extension/test/unit/app/controllers/metametrics-test.js
Mark Stacey b7033196d2
Add timeout to wait-until-called (#9996)
The `waitUntilCalled` utility now has a timeout. It will now throw an
error if the stub is not called enough times, rather than blocking
forever.

The return type had to be changed to a function, so that we could throw
when the timeout is triggered. I tried returning an error that rejected
first, but if you don't handle the error synchronously Node.js will
consider it to be an unhandled Promise rejected (even if it _is_
handled later on).

I worked around this by resolving in the timeout case as well, so that
there is never a "deferred" Promise exception in the timeout case. The
returned function re-throws the error if it's given. That way there is
never any unhandled Promise rejection.
2020-12-04 13:47:57 -03:30

547 lines
16 KiB
JavaScript

import { strict as assert } from 'assert'
import sinon from 'sinon'
import MetaMetricsController from '../../../../app/scripts/controllers/metametrics'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../../app/scripts/lib/enums'
import { createSegmentMock } from '../../../../app/scripts/lib/segment'
import {
METAMETRICS_ANONYMOUS_ID,
METAMETRICS_BACKGROUND_PAGE_OBJECT,
} from '../../../../shared/constants/metametrics'
import waitUntilCalled from '../../../lib/wait-until-called'
const segment = createSegmentMock(2, 10000)
const segmentLegacy = createSegmentMock(2, 10000)
const VERSION = '0.0.1-test'
const NETWORK = 'Mainnet'
const FAKE_CHAIN_ID = '0x1338'
const LOCALE = 'en_US'
const TEST_META_METRICS_ID = '0xabc'
const DEFAULT_TEST_CONTEXT = {
app: { name: 'MetaMask Extension', version: VERSION },
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
referrer: undefined,
userAgent: window.navigator.userAgent,
}
const DEFAULT_SHARED_PROPERTIES = {
chain_id: FAKE_CHAIN_ID,
locale: LOCALE.replace('_', '-'),
network: NETWORK,
environment_type: 'background',
}
const DEFAULT_EVENT_PROPERTIES = {
category: 'Unit Test',
revenue: undefined,
value: undefined,
currency: undefined,
...DEFAULT_SHARED_PROPERTIES,
}
const DEFAULT_PAGE_PROPERTIES = {
...DEFAULT_SHARED_PROPERTIES,
}
function getMockNetworkController(
chainId = FAKE_CHAIN_ID,
provider = { type: NETWORK },
) {
let networkStore = { chainId, provider }
const on = sinon.stub().withArgs('networkDidChange')
const updateState = (newState) => {
networkStore = { ...networkStore, ...newState }
on.getCall(0).args[1]()
}
return {
store: {
getState: () => networkStore,
updateState,
},
getCurrentChainId: () => networkStore.chainId,
getNetworkIdentifier: () => networkStore.provider.type,
on,
}
}
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,
}
}
function getMetaMetricsController({
participateInMetaMetrics = true,
metaMetricsId = TEST_META_METRICS_ID,
metaMetricsSendCount = 0,
preferencesStore = getMockPreferencesStore(),
networkController = getMockNetworkController(),
} = {}) {
return new MetaMetricsController({
segment,
segmentLegacy,
getNetworkIdentifier: networkController.getNetworkIdentifier.bind(
networkController,
),
getCurrentChainId: networkController.getCurrentChainId.bind(
networkController,
),
onNetworkDidChange: networkController.on.bind(
networkController,
'networkDidChange',
),
preferencesStore,
version: '0.0.1',
environment: 'test',
initState: {
participateInMetaMetrics,
metaMetricsId,
metaMetricsSendCount,
},
})
}
describe('MetaMetricsController', function () {
describe('constructor', function () {
it('should properly initialize', function () {
const metaMetricsController = getMetaMetricsController()
assert.strictEqual(metaMetricsController.version, VERSION)
assert.strictEqual(metaMetricsController.network, NETWORK)
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('_', '-'))
})
it('should update when network changes', function () {
const networkController = getMockNetworkController()
const metaMetricsController = getMetaMetricsController({
networkController,
})
assert.strictEqual(metaMetricsController.network, NETWORK)
networkController.store.updateState({
provider: {
type: 'NEW_NETWORK',
},
chainId: '0xaab',
})
assert.strictEqual(metaMetricsController.network, 'NEW_NETWORK')
assert.strictEqual(metaMetricsController.chainId, '0xaab')
})
it('should update when preferences changes', function () {
const preferencesStore = getMockPreferencesStore()
const metaMetricsController = getMetaMetricsController({
preferencesStore,
})
assert.strictEqual(metaMetricsController.network, NETWORK)
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('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('setMetaMetricsSendCount', function () {
it('should update the send count in state', function () {
const metaMetricsController = getMetaMetricsController()
metaMetricsController.setMetaMetricsSendCount(1)
assert.equal(metaMetricsController.state.metaMetricsSendCount, 1)
})
})
describe('trackEvent', 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.trackEvent({
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: false,
})
mock
.expects('track')
.once()
.withArgs({
event: 'Fake Event',
anonymousId: METAMETRICS_ANONYMOUS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
})
metaMetricsController.trackEvent(
{
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: false,
})
mock
.expects('track')
.once()
.withArgs({
event: 'Fake Event',
userId: 'TESTID',
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
})
metaMetricsController.trackEvent(
{
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(segmentLegacy)
const metaMetricsController = getMetaMetricsController()
mock
.expects('track')
.once()
.withArgs({
event: 'Fake Event',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
})
metaMetricsController.trackEvent(
{
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',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
})
metaMetricsController.trackEvent({
event: 'Fake Event',
category: 'Unit Test',
properties: {
test: 1,
},
})
mock.verify()
})
it('should use anonymousId when metametrics send count is not trackable in send flow', function () {
const mock = sinon.mock(segment)
const metaMetricsController = getMetaMetricsController({
metaMetricsSendCount: 1,
})
mock
.expects('track')
.once()
.withArgs({
event: 'Send Fake Event',
anonymousId: METAMETRICS_ANONYMOUS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
})
metaMetricsController.trackEvent({
event: 'Send Fake Event',
category: 'Unit Test',
properties: {
test: 1,
},
})
mock.verify()
})
it('should use user metametrics id when metametrics send count is trackable in send flow', function () {
const mock = sinon.mock(segment)
const metaMetricsController = getMetaMetricsController()
mock
.expects('track')
.once()
.withArgs({
event: 'Send Fake Event',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
})
metaMetricsController.trackEvent(
{
event: 'Send Fake Event',
category: 'Unit Test',
properties: {
test: 1,
},
},
{ metaMetricsSendCount: 0 },
)
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.trackEvent(
{
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.trackEvent({ event: 'test' }),
/Must specify event and category\./u,
'must specify category',
)
assert.rejects(
() => metaMetricsController.trackEvent({ 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.trackEvent(
{
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.trackEvent({
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,
},
}),
)
assert.ok(
spy.calledWith({
event: 'Fake Event',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: DEFAULT_EVENT_PROPERTIES,
}),
)
})
})
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,
},
})
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,
},
})
metaMetricsController.trackPage(
{
name: 'home',
params: null,
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
page: METAMETRICS_BACKGROUND_PAGE_OBJECT,
},
{ isOptInPath: true },
)
mock.verify()
})
})
afterEach(function () {
// flush the queues manually after each test
segment.flush()
segmentLegacy.flush()
sinon.restore()
})
})