-
Notifications
You must be signed in to change notification settings - Fork 5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Refactor migration 120.2 to prepare for additions (#26298)
## **Description** Migration 120.2 has been refactored to prepare for the addition of further obsolete state cleanup steps. A few minor functional changes and test changes have been made as well. The changes are: * The migration has been refactored to group together changes for each controller in separate functions. * The `SelectedNetworkController` migration has been updated to delete state rather than set it to default. This is functionally equivalent (for this controller) and it simplified the migration and tests a bit, avoiding the need to verify that the default state was correct. * When the `SnapController` state is invalid, we now capture a Sentry exception * A test has been added for this case as well * JSDoc comments have been added to the migration * Tests have been grouped together by controller * Tests have been added to ensure corrupted controller state does not impact the obsolete state removal steps of other controllers * Effectively each obsolete state removal function is acting as an independent migration here * A test was added for the case where `SnapController` state is not set * The tests have been updated to deep clone the input to guard against the migration failing to do so * The `transformState` function was given an explicit return type, and the unused return value was removed. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/26298?quickstart=1) ## **Related issues** Relates to #26297 ## **Manual testing steps** N/A ## **Screenshots/Recordings** N/A ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
- Loading branch information
Showing
2 changed files
with
223 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,218 @@ | ||
import { cloneDeep } from 'lodash'; | ||
import { migrate, version } from './120.2'; | ||
|
||
const sentryCaptureExceptionMock = jest.fn(); | ||
|
||
global.sentry = { | ||
captureException: sentryCaptureExceptionMock, | ||
}; | ||
|
||
const oldVersion = 120.1; | ||
|
||
describe('migration #120.2', () => { | ||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('updates the version metadata', async () => { | ||
const oldStorage = { | ||
meta: { version: oldVersion }, | ||
data: {}, | ||
}; | ||
|
||
const newStorage = await migrate(oldStorage); | ||
const newStorage = await migrate(cloneDeep(oldStorage)); | ||
|
||
expect(newStorage.meta).toStrictEqual({ version }); | ||
}); | ||
|
||
it('does nothing if SelectedNetworkController state is not set', async () => { | ||
const oldState = { | ||
OtherController: {}, | ||
}; | ||
describe('SelectedNetworkController', () => { | ||
it('does nothing if SelectedNetworkController state is not set', async () => { | ||
const oldState = { | ||
OtherController: {}, | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: oldState, | ||
}); | ||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data).toEqual(oldState); | ||
}); | ||
expect(transformedState.data).toEqual(oldState); | ||
}); | ||
|
||
it('sets SelectedNetworkController state to an object containing an empty object "domains" if SelectedNetworkController state is not itself an object', async () => { | ||
const oldState = { | ||
SelectedNetworkController: 'foo', | ||
}; | ||
it('removes SelectedNetworkController state if SelectedNetworkController state is not itself an object', async () => { | ||
const oldState = { | ||
SelectedNetworkController: 'foo', | ||
}; | ||
|
||
const expectedState = { | ||
SelectedNetworkController: { domains: {} }, | ||
}; | ||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: oldState, | ||
expect(transformedState.data).toEqual({}); | ||
}); | ||
|
||
expect(transformedState.data).toEqual(expectedState); | ||
}); | ||
|
||
it('removes "perDomainNetwork" property and resets "domains" object in SelectedNetworkController state if "perDomainNetwork" property is present', async () => { | ||
const oldState = { | ||
SelectedNetworkController: { | ||
domains: { | ||
'https://metamask.io': { | ||
network: 'mainnet', | ||
it('removes SelectedNetworkController state if "perDomainNetwork" property is present', async () => { | ||
const oldState = { | ||
SelectedNetworkController: { | ||
domains: { | ||
'https://metamask.io': { | ||
network: 'mainnet', | ||
}, | ||
}, | ||
perDomainNetwork: true, | ||
}, | ||
perDomainNetwork: true, | ||
}, | ||
}; | ||
}; | ||
|
||
const expectedState = { | ||
SelectedNetworkController: { | ||
domains: {}, | ||
}, | ||
}; | ||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: oldState, | ||
expect(transformedState.data).toEqual({}); | ||
}); | ||
|
||
expect(transformedState.data).toEqual(expectedState); | ||
}); | ||
|
||
it('leaves "domains" state unchanged in SelectedNetworkController if "perDomainNetwork" property is not present in SelectedNetworkController state', async () => { | ||
const oldState = { | ||
SelectedNetworkController: { | ||
domains: { | ||
'https://metamask.io': { | ||
network: 'mainnet', | ||
}, | ||
'https://test.io': { | ||
network: 'linea', | ||
}, | ||
'https://uniswap.io': { | ||
network: 'optimism', | ||
it('leaves "domains" state unchanged in SelectedNetworkController if "perDomainNetwork" property is not present in SelectedNetworkController state', async () => { | ||
const oldState = { | ||
SelectedNetworkController: { | ||
domains: { | ||
'https://metamask.io': { | ||
network: 'mainnet', | ||
}, | ||
'https://test.io': { | ||
network: 'linea', | ||
}, | ||
'https://uniswap.io': { | ||
network: 'optimism', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: oldState, | ||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data).toEqual(oldState); | ||
}); | ||
|
||
expect(transformedState.data).toEqual(oldState); | ||
it('still migrates SelectedNetworkController state if other controllers have invalid state', async () => { | ||
const oldState = { | ||
SelectedNetworkController: { | ||
domains: { | ||
'https://metamask.io': { | ||
network: 'mainnet', | ||
}, | ||
}, | ||
perDomainNetwork: true, | ||
}, | ||
SnapController: 'invalid', | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data.SelectedNetworkController).toBeUndefined(); | ||
}); | ||
}); | ||
|
||
it('strips SnapController.snapErrors if it exists', async () => { | ||
const oldState = { | ||
SnapController: { | ||
snapErrors: {}, | ||
snapStates: {}, | ||
unencryptedSnapStates: {}, | ||
snaps: {}, | ||
}, | ||
}; | ||
describe('SnapController', () => { | ||
it('does nothing if SnapController state is not set', async () => { | ||
const oldState = { | ||
PreferencesController: {}, | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: oldState, | ||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data).toEqual(oldState); | ||
}); | ||
|
||
expect(transformedState.data).toEqual({ | ||
SnapController: { snapStates: {}, unencryptedSnapStates: {}, snaps: {} }, | ||
it('captures an error and leaves state unchanged if SnapController state is corrupted', async () => { | ||
const oldState = { | ||
SnapController: 'invalid', | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data).toEqual(oldState); | ||
expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( | ||
new Error( | ||
`Migration ${version}: Invalid SnapController state of type 'string'`, | ||
), | ||
); | ||
}); | ||
}); | ||
|
||
it('does nothing if SnapController.snapErrors doesnt exist', async () => { | ||
const oldState = { | ||
SnapController: { | ||
it('strips SnapController.snapErrors if it exists', async () => { | ||
const oldState = { | ||
SnapController: { | ||
snapErrors: {}, | ||
snapStates: {}, | ||
unencryptedSnapStates: {}, | ||
snaps: {}, | ||
}, | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data).toEqual({ | ||
SnapController: { | ||
snapStates: {}, | ||
unencryptedSnapStates: {}, | ||
snaps: {}, | ||
}, | ||
}); | ||
}); | ||
|
||
it('does nothing if SnapController.snapErrors doesnt exist', async () => { | ||
const oldState = { | ||
SnapController: { | ||
snapStates: {}, | ||
unencryptedSnapStates: {}, | ||
snaps: {}, | ||
}, | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data).toEqual(oldState); | ||
}); | ||
|
||
it('still migrates SnapController state if other controllers have invalid state', async () => { | ||
const oldState = { | ||
SelectedNetworkController: 'invalid', | ||
SnapController: { | ||
snapErrors: {}, | ||
snapStates: {}, | ||
unencryptedSnapStates: {}, | ||
snaps: {}, | ||
}, | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: cloneDeep(oldState), | ||
}); | ||
|
||
expect(transformedState.data.SnapController).toEqual({ | ||
snapStates: {}, | ||
unencryptedSnapStates: {}, | ||
snaps: {}, | ||
}, | ||
}; | ||
|
||
const transformedState = await migrate({ | ||
meta: { version: oldVersion }, | ||
data: oldState, | ||
}); | ||
}); | ||
|
||
expect(transformedState.data).toEqual(oldState); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters