mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Capture exception with sentry when invariant conditions are met in migrations (#20427)
* capture exception for sentry when invariant conditions are met in migration 82 * Code cleanup * Capture exceptions in invariant conditions for migrations 83,84,85,86,89,91,93,94 * Update app/scripts/migrations/082.test.js Co-authored-by: Mark Stacey <markjstacey@gmail.com> * Code cleanup * Fix SentryObject type declaration * Stop throwing error if preferences controller is undefined * Refactor 084 and 086 to remove double negative * Capture exceptions for invariant states in in migrations 87,88,90 and 92 * lint fix * log warning in migration 82 when preferences controller is undefined --------- Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
parent
1ad47c660b
commit
915bf2ae88
@ -1,6 +1,12 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { migrate, version } from './082';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
@ -472,10 +478,72 @@ describe('migration #82', () => {
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalled();
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
});
|
||||
|
||||
it('should not change anything if there is no frequentRpcListDetail property on PreferencesController', async () => {
|
||||
it('should capture an exception if any PreferencesController.frequentRpcListDetail entries are not objects', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
transactionSecurityCheckEnabled: false,
|
||||
useBlockie: false,
|
||||
useCurrencyRateCheck: true,
|
||||
useMultiAccountBalanceChecker: true,
|
||||
useNftDetection: false,
|
||||
useNonceField: false,
|
||||
frequentRpcListDetail: [
|
||||
{
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'invalid entry type',
|
||||
1,
|
||||
],
|
||||
},
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(
|
||||
`state.PreferencesController.frequentRpcListDetail contains an element of type string`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not change anything, and not capture an exception, if there is no frequentRpcListDetail property on PreferencesController but there is a networkConfigurations object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
@ -556,9 +624,60 @@ describe('migration #82', () => {
|
||||
},
|
||||
};
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).not.toHaveBeenCalled();
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
});
|
||||
|
||||
it('should capture an exception if there is no frequentRpcListDetail property on PreferencesController and no networkConfiguration object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {
|
||||
transactionSecurityCheckEnabled: false,
|
||||
useBlockie: false,
|
||||
useCurrencyRateCheck: true,
|
||||
useMultiAccountBalanceChecker: true,
|
||||
useNftDetection: false,
|
||||
useNonceField: false,
|
||||
},
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(
|
||||
`typeof state.PreferencesController.frequentRpcListDetail is undefined`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should change nothing if PreferencesController is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -595,4 +714,61 @@ describe('migration #82', () => {
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
});
|
||||
|
||||
it('should capture an exception if PreferencesController is not an object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
network: '1',
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
},
|
||||
previousProviderStore: {
|
||||
chainId: '0x89',
|
||||
nickname: 'Polygon Mainnet',
|
||||
rpcPrefs: {},
|
||||
rpcUrl:
|
||||
'https://polygon-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'MATIC',
|
||||
type: 'rpc',
|
||||
},
|
||||
provider: {
|
||||
chainId: '0x1',
|
||||
nickname: '',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: '',
|
||||
ticker: 'ETH',
|
||||
type: 'mainnet',
|
||||
},
|
||||
},
|
||||
PreferencesController: false,
|
||||
},
|
||||
};
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.PreferencesController is boolean`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should capture an exception if NetworkController is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 81,
|
||||
},
|
||||
data: {
|
||||
PreferencesController: {},
|
||||
},
|
||||
};
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is undefined`),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { hasProperty, isObject } from '@metamask/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import log from 'loglevel';
|
||||
|
||||
export const version = 82;
|
||||
|
||||
@ -25,14 +26,56 @@ export async function migrate(originalVersionedData: {
|
||||
}
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (!hasProperty(state, 'PreferencesController')) {
|
||||
log.warn(`state.PreferencesController is undefined`);
|
||||
return state;
|
||||
}
|
||||
if (!isObject(state.PreferencesController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.PreferencesController is ${typeof state.PreferencesController}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
if (
|
||||
!hasProperty(state, 'PreferencesController') ||
|
||||
!isObject(state.PreferencesController) ||
|
||||
!isObject(state.NetworkController) ||
|
||||
!hasProperty(state.PreferencesController, 'frequentRpcListDetail') ||
|
||||
!Array.isArray(state.PreferencesController.frequentRpcListDetail) ||
|
||||
!state.PreferencesController.frequentRpcListDetail.every(isObject)
|
||||
!hasProperty(state, 'NetworkController') ||
|
||||
!isObject(state.NetworkController)
|
||||
) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController is ${typeof state.NetworkController}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
if (
|
||||
!hasProperty(state.PreferencesController, 'frequentRpcListDetail') ||
|
||||
!Array.isArray(state.PreferencesController.frequentRpcListDetail)
|
||||
) {
|
||||
const inPost077SupplementFor082State =
|
||||
state.NetworkController.networkConfigurations &&
|
||||
state.PreferencesController.frequentRpcListDetail === undefined;
|
||||
if (!inPost077SupplementFor082State) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.PreferencesController.frequentRpcListDetail is ${typeof state
|
||||
.PreferencesController.frequentRpcListDetail}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
if (!state.PreferencesController.frequentRpcListDetail.every(isObject)) {
|
||||
const erroneousElement =
|
||||
state.PreferencesController.frequentRpcListDetail.find(
|
||||
(element) => !isObject(element),
|
||||
);
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`state.PreferencesController.frequentRpcListDetail contains an element of type ${typeof erroneousElement}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
const { PreferencesController, NetworkController } = state;
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { migrate, version } from './083';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
@ -165,6 +171,24 @@ describe('migration #83', () => {
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
|
||||
it('should capture an exception if state.NetworkController is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not modify state if state.NetworkController is not an object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -190,6 +214,25 @@ describe('migration #83', () => {
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
|
||||
it('should capture an exception if state.NetworkController is not an object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: false,
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is boolean`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not modify state if state.NetworkController.networkConfigurations is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -221,6 +264,28 @@ describe('migration #83', () => {
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
|
||||
it('should capture an exception if state.NetworkController.networkConfigurations is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
testNetworkControllerProperty: 'testNetworkControllerValue',
|
||||
networkConfigurations: undefined,
|
||||
},
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof NetworkController.networkConfigurations is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not modify state if state.NetworkController.networkConfigurations is an empty object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
|
@ -25,11 +25,21 @@ export async function migrate(originalVersionedData: {
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (!isObject(state.NetworkController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController is ${typeof state.NetworkController}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
const { NetworkController } = state;
|
||||
|
||||
if (!isObject(NetworkController.networkConfigurations)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof NetworkController.networkConfigurations is ${typeof NetworkController.networkConfigurations}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { migrate } from './084';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
describe('migration #84', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('updates the version metadata', async () => {
|
||||
const originalVersionedData = buildOriginalVersionedData({
|
||||
meta: {
|
||||
@ -27,6 +37,21 @@ describe('migration #84', () => {
|
||||
expect(newVersionedData.data).toStrictEqual(originalVersionedData.data);
|
||||
});
|
||||
|
||||
it('captures an exception if the network controller state does not exist', async () => {
|
||||
const originalVersionedData = buildOriginalVersionedData({
|
||||
data: {
|
||||
test: '123',
|
||||
},
|
||||
});
|
||||
|
||||
await migrate(originalVersionedData);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
const nonObjects = [undefined, null, 'test', 1, ['test']];
|
||||
for (const invalidState of nonObjects) {
|
||||
it(`does not change the state if the network controller state is ${invalidState}`, async () => {
|
||||
@ -40,6 +65,21 @@ describe('migration #84', () => {
|
||||
|
||||
expect(newVersionedData.data).toStrictEqual(originalVersionedData.data);
|
||||
});
|
||||
|
||||
it(`captures an exception if the network controller state is ${invalidState}`, async () => {
|
||||
const originalVersionedData = buildOriginalVersionedData({
|
||||
data: {
|
||||
NetworkController: invalidState,
|
||||
},
|
||||
});
|
||||
|
||||
await migrate(originalVersionedData);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is ${typeof invalidState}`),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
it('does not change the state if the network controller state does not include "network"', async () => {
|
||||
@ -56,6 +96,38 @@ describe('migration #84', () => {
|
||||
expect(newVersionedData.data).toStrictEqual(originalVersionedData.data);
|
||||
});
|
||||
|
||||
it('captures an exception if the network controller state does not include "network" and does not include "networkId"', async () => {
|
||||
const originalVersionedData = buildOriginalVersionedData({
|
||||
data: {
|
||||
NetworkController: {
|
||||
test: '123',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await migrate(originalVersionedData);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController.network is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not capture an exception if the network controller state does not include "network" but does include "networkId"', async () => {
|
||||
const originalVersionedData = buildOriginalVersionedData({
|
||||
data: {
|
||||
NetworkController: {
|
||||
test: '123',
|
||||
networkId: 'foobar',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await migrate(originalVersionedData);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('replaces "network" in the network controller state with "networkId": null, "networkStatus": "unknown" if it is "loading"', async () => {
|
||||
const originalVersionedData = buildOriginalVersionedData({
|
||||
data: {
|
||||
|
@ -25,9 +25,26 @@ export async function migrate(originalVersionedData: {
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (
|
||||
!hasProperty(state, 'NetworkController') ||
|
||||
!isObject(state.NetworkController) ||
|
||||
!hasProperty(state.NetworkController, 'network')
|
||||
!isObject(state.NetworkController)
|
||||
) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController is ${typeof state.NetworkController}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
if (!hasProperty(state.NetworkController, 'network')) {
|
||||
const thePost077SupplementFor084HasNotModifiedState =
|
||||
state.NetworkController.networkId === undefined;
|
||||
if (thePost077SupplementFor084HasNotModifiedState) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController.network is ${typeof state
|
||||
.NetworkController.network}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { migrate, version } from './085';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
@ -10,6 +16,10 @@ jest.mock('uuid', () => {
|
||||
});
|
||||
|
||||
describe('migration #85', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -39,6 +49,25 @@ describe('migration #85', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception there is no network controller state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 84,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller previous provider state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
|
@ -24,6 +24,11 @@ export async function migrate(originalVersionedData: {
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (!isObject(state.NetworkController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController is ${typeof state.NetworkController}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { migrate, version } from './086';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
@ -10,6 +16,10 @@ jest.mock('uuid', () => {
|
||||
});
|
||||
|
||||
describe('migration #86', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -39,6 +49,25 @@ describe('migration #86', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception if there is no network controller state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 85,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller provider state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
@ -59,6 +88,52 @@ describe('migration #86', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception if there is no network controller provider state and no providerConfig state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 85,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController.provider is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not capture an exception if there is no network controller provider state but there is a providerConfig state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
foo: 'bar',
|
||||
},
|
||||
providerConfig: {},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 85,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should rename the provider config state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
|
@ -37,5 +37,24 @@ function transformState(state: Record<string, unknown>) {
|
||||
NetworkController: networkControllerState,
|
||||
};
|
||||
}
|
||||
if (!isObject(state.NetworkController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController is ${typeof state.NetworkController}`,
|
||||
),
|
||||
);
|
||||
} else if (!hasProperty(state.NetworkController, 'provider')) {
|
||||
const thePost077SupplementFor086HasNotModifiedState =
|
||||
state.NetworkController.providerConfig === undefined;
|
||||
if (thePost077SupplementFor086HasNotModifiedState) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController.provider is ${typeof state
|
||||
.NetworkController.provider}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { migrate, version } from './087';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
describe('migration #87', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -53,6 +63,65 @@ describe('migration #87', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should return state unaltered if TokensController state is not an object', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
TokensController: false,
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 86,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception if TokensController state is not an object', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
TokensController: false,
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 86,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokensController is boolean`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not capture an exception if TokensController state is an object', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
TokensController: {
|
||||
allDetectedTokens: {},
|
||||
allIgnoredTokens: {},
|
||||
allTokens: {},
|
||||
detectedTokens: [],
|
||||
ignoredTokens: [],
|
||||
suggestedAssets: [],
|
||||
tokens: [],
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 86,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should remove the suggested assets state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
|
@ -24,6 +24,11 @@ export async function migrate(originalVersionedData: {
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (!isObject(state.TokensController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.TokensController is ${typeof state.TokensController}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { migrate } from './088';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
describe('migration #88', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('updates the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
@ -26,6 +36,24 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if the NftController property is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
TokensController: {},
|
||||
NftController: false,
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NftController is boolean`),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the state unaltered if the NftController object has no allNftContracts property', async () => {
|
||||
const oldData = {
|
||||
NftController: {
|
||||
@ -58,6 +86,26 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if it NftController.allNftContracts is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
TokensController: {},
|
||||
NftController: {
|
||||
allNftContracts: 'foo',
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NftController.allNftContracts is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the state unaltered if any value of the NftController.allNftContracts object is not an object itself', async () => {
|
||||
const oldData = {
|
||||
NftController: {
|
||||
@ -324,6 +372,26 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if it NftController.allNfts is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
TokensController: {},
|
||||
NftController: {
|
||||
allNfts: 'foo',
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NftController.allNfts is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the state unaltered if any value of the NftController.allNfts object is not an object itself', async () => {
|
||||
const oldData = {
|
||||
NftController: {
|
||||
@ -656,6 +724,91 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if it has no TokenListController property', async () => {
|
||||
const oldData = {
|
||||
TokensController: {},
|
||||
NftController: {
|
||||
allNfts: {
|
||||
'0x111': {
|
||||
'0x10': [
|
||||
{
|
||||
name: 'NFT 1',
|
||||
description: 'Description for NFT 1',
|
||||
image: 'nft1.jpg',
|
||||
standard: 'ERC721',
|
||||
tokenId: '1',
|
||||
address: '0xaaa',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
allNftContracts: {
|
||||
'0x111': {
|
||||
'0x10': [
|
||||
{
|
||||
name: 'Contract 1',
|
||||
address: '0xaaa',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokenListController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('captures an exception if the TokenListController property is not an object', async () => {
|
||||
const oldData = {
|
||||
TokensController: {},
|
||||
NftController: {
|
||||
allNfts: {
|
||||
'0x111': {
|
||||
'0x10': [
|
||||
{
|
||||
name: 'NFT 1',
|
||||
description: 'Description for NFT 1',
|
||||
image: 'nft1.jpg',
|
||||
standard: 'ERC721',
|
||||
tokenId: '1',
|
||||
address: '0xaaa',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
allNftContracts: {
|
||||
'0x111': {
|
||||
'0x10': [
|
||||
{
|
||||
name: 'Contract 1',
|
||||
address: '0xaaa',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
TokenListController: false,
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokenListController is boolean`),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the state unaltered if the TokenListController object has no tokensChainsCache property', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {
|
||||
@ -688,6 +841,25 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if the TokenListController.tokensChainsCache property is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {
|
||||
tokensChainsCache: 'foo',
|
||||
},
|
||||
TokensController: {},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokenListController.tokensChainsCache is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites TokenListController.tokensChainsCache so that decimal chain IDs are converted to hex strings', async () => {
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
@ -919,6 +1091,39 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if it has no TokensController property', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokensController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('captures an exception if the TokensController property is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
TokensController: false,
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokensController is boolean`),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the state unaltered if the TokensController object has no allTokens property', async () => {
|
||||
const oldData = {
|
||||
TokensController: {
|
||||
@ -951,6 +1156,25 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if the TokensController.allTokens property is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
TokensController: {
|
||||
allTokens: 'foo',
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokensController.allTokens is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites TokensController.allTokens so that decimal chain IDs are converted to hex strings', async () => {
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
@ -1163,6 +1387,25 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if the TokensController.allIgnoredTokens property is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
TokensController: {
|
||||
allIgnoredTokens: 'foo',
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokensController.allIgnoredTokens is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites TokensController.allIgnoredTokens so that decimal chain IDs are converted to hex strings', async () => {
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
@ -1323,6 +1566,25 @@ describe('migration #88', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if the TokensController.allDetectedTokens property is not an object', async () => {
|
||||
const oldData = {
|
||||
TokenListController: {},
|
||||
TokensController: {
|
||||
allDetectedTokens: 'foo',
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.TokensController.allDetectedTokens is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites TokensController.allDetectedTokens so that decimal chain IDs are converted to hex strings', async () => {
|
||||
const oldStorage = {
|
||||
meta: { version: 87 },
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { hasProperty, Hex, isObject, isStrictHexString } from '@metamask/utils';
|
||||
import { BN } from 'ethereumjs-util';
|
||||
import { cloneDeep, mapKeys } from 'lodash';
|
||||
import log from 'loglevel';
|
||||
|
||||
type VersionedData = {
|
||||
meta: { version: number };
|
||||
@ -70,6 +71,16 @@ function migrateData(state: Record<string, unknown>): void {
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (hasProperty(nftControllerState, 'allNftContracts')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.warn(
|
||||
`typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Migrate NftController.allNfts
|
||||
@ -96,9 +107,25 @@ function migrateData(state: Record<string, unknown>): void {
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (hasProperty(nftControllerState, 'allNfts')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.warn(
|
||||
`typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`,
|
||||
);
|
||||
}
|
||||
|
||||
state.NftController = nftControllerState;
|
||||
} else if (hasProperty(state, 'NftController')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(`typeof state.NftController is ${typeof state.NftController}`),
|
||||
);
|
||||
} else {
|
||||
log.warn(`typeof state.NftController is undefined`);
|
||||
}
|
||||
|
||||
if (
|
||||
@ -124,7 +151,24 @@ function migrateData(state: Record<string, unknown>): void {
|
||||
tokenListControllerState.tokensChainsCache,
|
||||
(_, chainId: string) => toHex(chainId),
|
||||
);
|
||||
} else if (hasProperty(tokenListControllerState, 'tokensChainsCache')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.TokenListController.tokensChainsCache is ${typeof state
|
||||
.TokenListController.tokensChainsCache}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.warn(
|
||||
`typeof state.TokenListController.tokensChainsCache is undefined`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.TokenListController is ${typeof state.TokenListController}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
@ -150,6 +194,16 @@ function migrateData(state: Record<string, unknown>): void {
|
||||
allTokens,
|
||||
(_, chainId: string) => toHex(chainId),
|
||||
);
|
||||
} else if (hasProperty(tokensControllerState, 'allTokens')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.warn(
|
||||
`typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Migrate TokensController.allIgnoredTokens
|
||||
@ -169,6 +223,16 @@ function migrateData(state: Record<string, unknown>): void {
|
||||
allIgnoredTokens,
|
||||
(_, chainId: string) => toHex(chainId),
|
||||
);
|
||||
} else if (hasProperty(tokensControllerState, 'allIgnoredTokens')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.warn(
|
||||
`typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Migrate TokensController.allDetectedTokens
|
||||
@ -188,9 +252,25 @@ function migrateData(state: Record<string, unknown>): void {
|
||||
allDetectedTokens,
|
||||
(_, chainId: string) => toHex(chainId),
|
||||
);
|
||||
} else if (hasProperty(tokensControllerState, 'allDetectedTokens')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.warn(
|
||||
`typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`,
|
||||
);
|
||||
}
|
||||
|
||||
state.TokensController = tokensControllerState;
|
||||
} else {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.TokensController is ${typeof state.TokensController}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,14 @@
|
||||
import { migrate, version } from './089';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
startSession: jest.fn(),
|
||||
endSession: jest.fn(),
|
||||
toggleSession: jest.fn(),
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
@ -10,6 +19,10 @@ jest.mock('uuid', () => {
|
||||
});
|
||||
|
||||
describe('migration #89', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -39,6 +52,25 @@ describe('migration #89', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception if there is no network controller state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller providerConfig state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
@ -61,6 +93,32 @@ describe('migration #89', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception if there is no network controller providerConfig state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
id1: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 88,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController.providerConfig is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return state unaltered if the providerConfig already has an id', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
|
@ -66,6 +66,19 @@ function transformState(state: Record<string, unknown>) {
|
||||
...state,
|
||||
NetworkController: state.NetworkController,
|
||||
};
|
||||
} else if (!isObject(state.NetworkController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController is ${typeof state.NetworkController}`,
|
||||
),
|
||||
);
|
||||
} else if (!isObject(state.NetworkController.providerConfig)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController.providerConfig is ${typeof state
|
||||
.NetworkController.providerConfig}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
@ -2,7 +2,17 @@ import { migrate, version } from './090';
|
||||
|
||||
const PREVIOUS_VERSION = version - 1;
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
describe('migration #90', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('updates the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -31,7 +41,7 @@ describe('migration #90', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
});
|
||||
|
||||
it('does not change the state if the phishing controller state is invalid', async () => {
|
||||
it('captures an exception if the phishing controller state is invalid', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: PREVIOUS_VERSION,
|
||||
@ -39,9 +49,12 @@ describe('migration #90', () => {
|
||||
data: { PhishingController: 'this is not valid' },
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(newStorage.data).toStrictEqual(oldStorage.data);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.PhishingController is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not change the state if the listState property does not exist', async () => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { hasProperty, isObject } from '@metamask/utils';
|
||||
import log from 'loglevel';
|
||||
|
||||
export const version = 90;
|
||||
|
||||
@ -23,11 +24,22 @@ export async function migrate(originalVersionedData: {
|
||||
}
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (
|
||||
!hasProperty(state, 'PhishingController') ||
|
||||
!isObject(state.PhishingController) ||
|
||||
!hasProperty(state.PhishingController, 'listState')
|
||||
) {
|
||||
if (!hasProperty(state, 'PhishingController')) {
|
||||
log.warn(`typeof state.PhishingController is undefined`);
|
||||
return state;
|
||||
}
|
||||
if (!isObject(state.PhishingController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.PhishingController is ${typeof state.PhishingController}`,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
if (!hasProperty(state.PhishingController, 'listState')) {
|
||||
log.warn(
|
||||
`typeof state.PhishingController.listState is ${typeof state.PhishingController}`,
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { migrate, version } from './091';
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
startSession: jest.fn(),
|
||||
endSession: jest.fn(),
|
||||
toggleSession: jest.fn(),
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
@ -11,6 +20,10 @@ jest.mock('uuid', () => {
|
||||
});
|
||||
|
||||
describe('migration #91', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -40,6 +53,25 @@ describe('migration #91', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception if there is no network controller state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 90,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.NetworkController is undefined`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller networkConfigurations state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
@ -60,6 +92,32 @@ describe('migration #91', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should capture an exception if there is no network controller networkConfigurations state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
providerConfig: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 90,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(
|
||||
`typeof state.NetworkController.networkConfigurations is undefined`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return state unaltered if the networkConfigurations all have a chainId', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
|
@ -50,6 +50,19 @@ function transformState(state: Record<string, unknown>) {
|
||||
...state,
|
||||
NetworkController: state.NetworkController,
|
||||
};
|
||||
} else if (!isObject(state.NetworkController)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController is ${typeof state.NetworkController}`,
|
||||
),
|
||||
);
|
||||
} else if (!isObject(state.NetworkController.networkConfigurations)) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.NetworkController.networkConfigurations is ${typeof state
|
||||
.NetworkController.networkConfigurations}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
@ -3,7 +3,20 @@ import { migrate, version } from './092';
|
||||
|
||||
const PREVIOUS_VERSION = version - 1;
|
||||
|
||||
const sentryCaptureExceptionMock = jest.fn();
|
||||
|
||||
global.sentry = {
|
||||
startSession: jest.fn(),
|
||||
endSession: jest.fn(),
|
||||
toggleSession: jest.fn(),
|
||||
captureException: sentryCaptureExceptionMock,
|
||||
};
|
||||
|
||||
describe('migration #92', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
@ -33,6 +46,22 @@ describe('migration #92', () => {
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('captures an exception if the phishing controller state is invalid', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: PREVIOUS_VERSION,
|
||||
},
|
||||
data: { PhishingController: 'this is not valid' },
|
||||
};
|
||||
|
||||
await migrate(oldStorage);
|
||||
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
|
||||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
|
||||
new Error(`typeof state.PhishingController is string`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no phishing controller last fetched state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { hasProperty, isObject } from '@metamask/utils';
|
||||
import log from 'loglevel';
|
||||
|
||||
export const version = 92;
|
||||
|
||||
@ -30,6 +31,14 @@ function transformState(state: Record<string, unknown>) {
|
||||
) {
|
||||
delete state.PhishingController.stalelistLastFetched;
|
||||
delete state.PhishingController.hotlistLastFetched;
|
||||
} else if (hasProperty(state, 'PhishingController')) {
|
||||
global.sentry?.captureException?.(
|
||||
new Error(
|
||||
`typeof state.PhishingController is ${typeof state.PhishingController}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.warn(`typeof state.PhishingController is undefined`);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
4
types/global.d.ts
vendored
4
types/global.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
// In order for variables to be considered on the global scope they must be
|
||||
// declared using var and not const or let, which is why this rule is disabled
|
||||
/* eslint-disable no-var */
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
declare class Platform {
|
||||
openTab: (opts: { url: string }) => void;
|
||||
@ -11,6 +12,9 @@ declare class Platform {
|
||||
export declare global {
|
||||
var platform: Platform;
|
||||
|
||||
// Sentry is undefined in dev, so use optional chaining
|
||||
var sentry: Sentry | undefined;
|
||||
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
toBeFulfilled(): Promise<R>;
|
||||
|
Loading…
Reference in New Issue
Block a user