Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions app/store/migrations/110.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import migrate from './110';
import { ensureValidState } from './util';
import { captureException } from '@sentry/react-native';

jest.mock('@sentry/react-native', () => ({
captureException: jest.fn(),
}));

jest.mock('./util', () => ({
ensureValidState: jest.fn(),
}));

const mockedCaptureException = jest.mocked(captureException);
const mockedEnsureValidState = jest.mocked(ensureValidState);

describe('Migration 110: Remove SwapsController state', () => {
beforeEach(() => {
jest.resetAllMocks();
});

it('returns state unchanged if ensureValidState fails', () => {
const state = { some: 'state' };

mockedEnsureValidState.mockReturnValue(false);

const migratedState = migrate(state);

expect(migratedState).toBe(state);
expect(mockedCaptureException).not.toHaveBeenCalled();
});

it('returns state unchanged if backgroundState is missing', () => {
const state = {
engine: {},
};

mockedEnsureValidState.mockReturnValue(true);

const migratedState = migrate(state);

expect(migratedState).toEqual(state);
expect(mockedCaptureException).not.toHaveBeenCalled();
});

it('removes SwapsController from backgroundState', () => {
interface TestState {
engine: {
backgroundState: {
SwapsController?: {
chainCache: Record<string, unknown>;
};
BridgeController: {
someProperty: string;
};
OtherController: {
shouldStayUntouched: boolean;
};
};
};
}

const state: TestState = {
engine: {
backgroundState: {
SwapsController: {
chainCache: {
'0x1': { someData: 'value' },
},
},
BridgeController: {
someProperty: 'should remain',
},
OtherController: {
shouldStayUntouched: true,
},
},
},
};

mockedEnsureValidState.mockReturnValue(true);

const migratedState = migrate(state) as typeof state;

expect(
migratedState.engine.backgroundState.SwapsController,
).toBeUndefined();

expect(migratedState.engine.backgroundState.BridgeController).toEqual({
someProperty: 'should remain',
});
expect(migratedState.engine.backgroundState.OtherController).toEqual({
shouldStayUntouched: true,
});

expect(mockedCaptureException).not.toHaveBeenCalled();
});

it('leaves state unchanged if SwapsController does not exist', () => {
interface TestState {
engine: {
backgroundState: {
BridgeController: {
someProperty: string;
};
OtherController: {
shouldStayUntouched: boolean;
};
};
};
}

const state: TestState = {
engine: {
backgroundState: {
BridgeController: {
someProperty: 'should remain',
},
OtherController: {
shouldStayUntouched: true,
},
},
},
};

mockedEnsureValidState.mockReturnValue(true);

const migratedState = migrate(state) as typeof state;

expect(migratedState).toEqual(state);
expect(mockedCaptureException).not.toHaveBeenCalled();
});

it('handles error during migration', () => {
const state = {
engine: {
backgroundState: Object.defineProperty({}, 'SwapsController', {
get: () => {
throw new Error('Test error');
},
configurable: true,
enumerable: true,
}),
},
};

mockedEnsureValidState.mockReturnValue(true);

const migratedState = migrate(state);

expect(migratedState).toEqual(state);
expect(mockedCaptureException).toHaveBeenCalledWith(expect.any(Error));
expect(mockedCaptureException.mock.calls[0][0].message).toContain(
'Migration 110 failed',
);
});
});
41 changes: 41 additions & 0 deletions app/store/migrations/110.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { hasProperty, isObject } from '@metamask/utils';
import { ensureValidState } from './util';
import { captureException } from '@sentry/react-native';

/**
* Migration 110: Remove SwapsController state
*
* This migration removes the entire SwapsController from backgroundState
* as it's no longer used (functionality moved to BridgeController/unified swaps).
*/
const migration = (state: unknown): unknown => {
const migrationVersion = 110;

if (!ensureValidState(state, migrationVersion)) {
return state;
}

try {
const backgroundState = state?.engine?.backgroundState;

if (!backgroundState) {
return state;
}

if (
hasProperty(backgroundState, 'SwapsController') &&
isObject(backgroundState.SwapsController)
) {
delete backgroundState.SwapsController;
}

return state;
} catch (error) {
captureException(
new Error(`Migration ${migrationVersion} failed: ${error}`),
);
return state;
}
};

export default migration;
2 changes: 2 additions & 0 deletions app/store/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import migration106 from './106';
import migration107 from './107';
import migration108 from './108';
import migration109 from './109';
import migration110 from './110';

// Add migrations above this line
import { ControllerStorage } from '../persistConfig';
Expand Down Expand Up @@ -239,6 +240,7 @@ export const migrationList: MigrationsList = {
107: migration107,
108: migration108,
109: migration109,
110: migration110,
};

// Enable both synchronous and asynchronous migrations
Expand Down
Loading