/* eslint-disable no-useless-escape */ const { isEqual, omit } = require('lodash'); const { defaultGanacheOptions, withFixtures, unlockWallet, sendTransaction, getEventPayloads, assertInAnyOrder, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); /** * mocks the segment api multiple times for specific payloads that we expect to * see when these tests are run. In this case we are looking for * 'Transaction Submitted' and 'Transaction Finalized'. In addition on the * first event of each series we require a field that should only appear in the * anonymized events so that we can guarantee order of seenRequests and can * properly make assertions. Do not use the constants from the metrics * constants files, because if these change we want a strong indicator to our * data team that the shape of data will change. * * @param {import('mockttp').Mockttp} mockServer * @returns {Promise[]} */ async function mockSegment(mockServer) { return [ await mockServer .forPost('https://api.segment.io/v1/batch') .withJsonBodyIncluding({ batch: [ { type: 'track', event: 'Transaction Submitted', properties: { status: 'submitted' }, }, ], }) .thenCallback(() => { return { statusCode: 200, }; }), await mockServer .forPost('https://api.segment.io/v1/batch') .withJsonBodyIncluding({ batch: [ { type: 'track', event: 'Transaction Submitted', }, ], }) .thenCallback(() => { return { statusCode: 200, }; }), await mockServer .forPost('https://api.segment.io/v1/batch') .withJsonBodyIncluding({ batch: [ { type: 'track', event: 'Transaction Finalized', properties: { status: 'confirmed' }, }, ], }) .thenCallback(() => { return { statusCode: 200, }; }), await mockServer .forPost('https://api.segment.io/v1/batch') .withJsonBodyIncluding({ batch: [ { type: 'track', event: 'Transaction Finalized', }, ], }) .thenCallback(() => { return { statusCode: 200, }; }), ]; } const RECIPIENT = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; /** * Assert that the event names begin with the appropriate prefixes. Even * finalized events begin with transaction-submitted because they start as * event fragments created when the transaction is submitted. * * @param {object} payload */ const messageIdStartsWithTransactionSubmitted = (payload) => payload.messageId.startsWith('transaction-submitted'); /** * Assert that the events with sensitive properties should have messageIds * ending in 0x000 this is important because otherwise the events are seen as * duplicates in segment * * @param {object} payload */ const messageIdEndsWithZeros = (payload) => payload.messageId.endsWith('0x000'); /** * Assert that the events with sensitive data do not contain a userId (the * random anonymous id generated when a user opts into metametrics) * * @param {object} payload */ const eventDoesNotIncludeUserId = (payload) => typeof payload.userId === 'undefined'; const eventHasUserIdWithoutAnonymousId = (payload) => typeof payload.userId === 'string' && typeof payload.anonymousId === 'undefined'; /** * Assert that the events with sensitive data have anonymousId set to * 0x0000000000000000 which is our universal anonymous record * * @param {object} payload */ const eventHasZeroAddressAnonymousId = (payload) => payload.anonymousId === '0x0000000000000000'; describe('Transaction Finalized Event', function () { it('Successfully tracked when sending a transaction', async function () { await withFixtures( { fixtures: new FixtureBuilder() .withMetaMetricsController({ metaMetricsId: 'fake-metrics-id', participateInMetaMetrics: true, }) .build(), ganacheOptions: defaultGanacheOptions, title: this.test.title, testSpecificMock: mockSegment, }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { await driver.navigate(); await unlockWallet(driver); await sendTransaction(driver, RECIPIENT, '2.0'); const events = await getEventPayloads(driver, mockedEndpoints); const transactionSubmittedWithSensitivePropertiesAssertions = [ messageIdStartsWithTransactionSubmitted, messageIdEndsWithZeros, eventDoesNotIncludeUserId, eventHasZeroAddressAnonymousId, (payload) => isEqual(omit(payload.properties, ['first_seen']), { status: 'submitted', transaction_envelope_type: 'legacy', gas_limit: '0x5208', gas_price: '2', default_gas: '0.000021', default_gas_price: '2', chain_id: '0x539', referrer: 'metamask', source: 'user', network: '1337', eip_1559_version: '0', gas_edit_type: 'none', gas_edit_attempted: 'none', account_type: 'MetaMask', device_model: 'N/A', asset_type: 'NATIVE', token_standard: 'NONE', transaction_type: 'simpleSend', transaction_speed_up: false, ui_customizations: null, category: 'Transactions', locale: 'en', environment_type: 'background', }), ]; const transactionSubmittedWithoutSensitivePropertiesAssertions = [ messageIdStartsWithTransactionSubmitted, eventHasUserIdWithoutAnonymousId, (payload) => isEqual(payload.properties, { chain_id: '0x539', referrer: 'metamask', source: 'user', network: '1337', eip_1559_version: '0', gas_edit_type: 'none', gas_edit_attempted: 'none', account_type: 'MetaMask', device_model: 'N/A', asset_type: 'NATIVE', token_standard: 'NONE', transaction_type: 'simpleSend', transaction_speed_up: false, ui_customizations: null, category: 'Transactions', locale: 'en', environment_type: 'background', status: 'submitted', }), ]; const transactionFinalizedWithSensitivePropertiesAssertions = [ messageIdStartsWithTransactionSubmitted, messageIdEndsWithZeros, eventDoesNotIncludeUserId, eventHasZeroAddressAnonymousId, (payload) => isEqual( omit(payload.properties, ['first_seen', 'completion_time']), { status: 'confirmed', transaction_envelope_type: 'legacy', gas_limit: '0x5208', gas_price: '2', default_gas: '0.000021', default_gas_price: '2', chain_id: '0x539', referrer: 'metamask', source: 'user', network: '1337', eip_1559_version: '0', gas_edit_type: 'none', gas_edit_attempted: 'none', account_type: 'MetaMask', device_model: 'N/A', asset_type: 'NATIVE', token_standard: 'NONE', transaction_type: 'simpleSend', transaction_speed_up: false, ui_customizations: null, gas_used: '5208', category: 'Transactions', locale: 'en', environment_type: 'background', }, ), ]; const transactionFinalizedWithoutSensitivePropertiesAssertions = [ messageIdStartsWithTransactionSubmitted, eventHasUserIdWithoutAnonymousId, (payload) => isEqual(payload.properties, { chain_id: '0x539', referrer: 'metamask', source: 'user', network: '1337', eip_1559_version: '0', gas_edit_type: 'none', gas_edit_attempted: 'none', account_type: 'MetaMask', device_model: 'N/A', asset_type: 'NATIVE', token_standard: 'NONE', transaction_type: 'simpleSend', transaction_speed_up: false, ui_customizations: null, category: 'Transactions', locale: 'en', environment_type: 'background', status: 'confirmed', }), ]; const [event1, event2, event3, event4] = events; assertInAnyOrder( [event1, event2, event3, event4], [ transactionSubmittedWithSensitivePropertiesAssertions, transactionSubmittedWithoutSensitivePropertiesAssertions, transactionFinalizedWithSensitivePropertiesAssertions, transactionFinalizedWithoutSensitivePropertiesAssertions, ], ); }, ); }); });