diff --git a/jest.config.js b/jest.config.js index f1d38ab4aea3..56a75bfc68ed 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,6 +35,7 @@ module.exports = { '/development/**/*.test.(js|ts|tsx)', '/test/unit-global/**/*.test.(js|ts|tsx)', '/test/e2e/helpers.test.js', + '/test/e2e/helpers/**/*.test.(js|ts|tsx)', ], testPathIgnorePatterns: ['/development/webpack/'], testTimeout: 5500, diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index d73b959946c2..29770343488d 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -57,6 +57,7 @@ function onboardingFixture() { }), providerConfig: { id: 'networkConfigurationId' }, }, + NotificationServicesController: {}, PreferencesController: { advancedGasFee: {}, currentLocale: 'en', @@ -138,6 +139,7 @@ function onboardingFixture() { }, }, }, + UserStorageController: {}, TokensController: { allDetectedTokens: {}, allIgnoredTokens: {}, diff --git a/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts b/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts new file mode 100644 index 000000000000..1b6591899c0e --- /dev/null +++ b/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts @@ -0,0 +1,304 @@ +import * as mockttp from 'mockttp'; +import { UserStorageMockttpController } from './userStorageMockttpController'; + +describe('UserStorageMockttpController', () => { + let mockServer: mockttp.Mockttp; + + const baseUrl = 'https://user-storage.api.cx.metamask.io/api/v1/userstorage'; + + describe('mimics user storage behaviour', () => { + mockServer = mockttp.getLocal({ cors: true }); + + it('handles GET requests that have empty response', async () => { + const controller = new UserStorageMockttpController(); + + controller.setupPath('accounts', mockServer); + + const request = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(request.json).toEqual(null); + }); + + it('handles GET requests that have a pre-defined response', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const request = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(request.json).toEqual(mockedData); + }); + + it('handles batch GET requests', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const request = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(request.json).toEqual(mockedData); + }); + + it('handles GET requests for feature entries', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const request = await controller.onGet('accounts', { + path: `${baseUrl}/accounts/7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b`, + }); + + expect(request.json).toEqual(mockedData[0]); + }); + + it('handles PUT requests to create new entries', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + const mockedAddedData = { + HashedKey: + '6afbe024087495b4e0d56c4bdfc981c84eba44a7c284d4f455b5db4fcabc2173', + Data: 'data3', + }; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const putRequest = await controller.onPut('accounts', { + path: `${baseUrl}/accounts/6afbe024087495b4e0d56c4bdfc981c84eba44a7c284d4f455b5db4fcabc2173`, + body: { + getJson: async () => ({ + data: mockedAddedData.Data, + }), + } as unknown as mockttp.CompletedBody, + }); + + expect(putRequest.statusCode).toEqual(204); + + const getRequest = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(getRequest.json).toEqual([...mockedData, mockedAddedData]); + }); + + it('handles PUT requests to update existing entries', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + const mockedUpdatedData = { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data3', + }; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const putRequest = await controller.onPut('accounts', { + path: `${baseUrl}/accounts/c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468`, + body: { + getJson: async () => ({ + data: mockedUpdatedData.Data, + }), + } as unknown as mockttp.CompletedBody, + }); + + expect(putRequest.statusCode).toEqual(204); + + const getRequest = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(getRequest.json).toEqual([mockedData[0], mockedUpdatedData]); + }); + + it('handles batch PUT requests', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + const mockedUpdatedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data3', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data4', + }, + ]; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const putData = {} as { [key: string]: string }; + mockedUpdatedData.forEach((entry) => { + putData[entry.HashedKey] = entry.Data; + }); + + const putRequest = await controller.onPut('accounts', { + path: `${baseUrl}/accounts`, + body: { + getJson: async () => ({ + data: putData, + }), + } as unknown as mockttp.CompletedBody, + }); + + expect(putRequest.statusCode).toEqual(204); + + const getRequest = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(getRequest.json).toEqual(mockedUpdatedData); + }); + + it('handles DELETE requests', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const deleteRequest = await controller.onDelete('accounts', { + path: `${baseUrl}/accounts/c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468`, + }); + + expect(deleteRequest.statusCode).toEqual(204); + + const getRequest = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(getRequest.json).toEqual([mockedData[0]]); + }); + + it('handles batch DELETE requests', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + + controller.setupPath('accounts', mockServer, { + getResponse: mockedData, + }); + + const deleteRequest = await controller.onDelete('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(deleteRequest.statusCode).toEqual(204); + + const getRequest = await controller.onGet('accounts', { + path: `${baseUrl}/accounts`, + }); + + expect(getRequest.json).toEqual(null); + }); + }); +}); diff --git a/test/e2e/helpers/user-storage/userStorageMockttpController.ts b/test/e2e/helpers/user-storage/userStorageMockttpController.ts new file mode 100644 index 000000000000..970a10d11120 --- /dev/null +++ b/test/e2e/helpers/user-storage/userStorageMockttpController.ts @@ -0,0 +1,196 @@ +import { CompletedRequest, Mockttp } from 'mockttp'; + +// TODO: Export user storage schema from @metamask/profile-sync-controller +export const pathRegexps = { + accounts: + /https:\/\/user-storage\.api\.cx\.metamask\.io\/api\/v1\/userstorage\/accounts/u, + networks: + /https:\/\/user-storage\.api\.cx\.metamask\.io\/api\/v1\/userstorage\/networks/u, + notifications: + /https:\/\/user-storage\.api\.cx\.metamask\.io\/api\/v1\/userstorage\/notifications/u, +}; + +type UserStorageResponseData = { HashedKey: string; Data: string }; + +const determineIfFeatureEntryFromURL = (url: string) => + url.substring(url.lastIndexOf('userstorage') + 12).split('/').length === 2; + +export class UserStorageMockttpController { + paths: Map< + keyof typeof pathRegexps, + { + response: UserStorageResponseData[]; + server: Mockttp; + } + > = new Map(); + + readonly onGet = async ( + path: keyof typeof pathRegexps, + request: Pick, + statusCode: number = 200, + ) => { + const internalPathData = this.paths.get(path); + + if (!internalPathData) { + return { + statusCode, + json: null, + }; + } + + const isFeatureEntry = determineIfFeatureEntryFromURL(request.path); + + if (isFeatureEntry) { + const json = + internalPathData.response?.find( + (entry) => entry.HashedKey === request.path.split('/').pop(), + ) || null; + + return { + statusCode, + json, + }; + } + + const json = internalPathData?.response.length + ? internalPathData.response + : null; + + return { + statusCode, + json, + }; + }; + + readonly onPut = async ( + path: keyof typeof pathRegexps, + request: Pick, + statusCode: number = 204, + ) => { + const isFeatureEntry = determineIfFeatureEntryFromURL(request.path); + + const data = (await request.body.getJson()) as { + data: string | { [key: string]: string }; + }; + + const newOrUpdatedSingleOrBatchEntries = + isFeatureEntry && typeof data?.data === 'string' + ? [ + { + HashedKey: request.path.split('/').pop() as string, + Data: data?.data, + }, + ] + : Object.entries(data?.data).map(([key, value]) => ({ + HashedKey: key, + Data: value, + })); + + newOrUpdatedSingleOrBatchEntries.forEach((entry) => { + const internalPathData = this.paths.get(path); + + if (!internalPathData) { + return; + } + + const doesThisEntryExist = internalPathData.response?.find( + (existingEntry) => existingEntry.HashedKey === entry.HashedKey, + ); + + if (doesThisEntryExist) { + this.paths.set(path, { + ...internalPathData, + response: internalPathData.response.map((existingEntry) => + existingEntry.HashedKey === entry.HashedKey ? entry : existingEntry, + ), + }); + } else { + this.paths.set(path, { + ...internalPathData, + response: [ + ...(internalPathData?.response || []), + entry as { HashedKey: string; Data: string }, + ], + }); + } + }); + + return { + statusCode, + }; + }; + + readonly onDelete = async ( + path: keyof typeof pathRegexps, + request: Pick, + statusCode: number = 204, + ) => { + const internalPathData = this.paths.get(path); + + if (!internalPathData) { + return { + statusCode, + }; + } + + const isFeatureEntry = determineIfFeatureEntryFromURL(request.path); + + if (isFeatureEntry) { + this.paths.set(path, { + ...internalPathData, + response: internalPathData?.response.filter( + (entry) => entry.HashedKey !== request.path.split('/').pop(), + ), + }); + } else { + this.paths.set(path, { + ...internalPathData, + response: [], + }); + } + + return { + statusCode, + }; + }; + + setupPath = ( + path: keyof typeof pathRegexps, + server: Mockttp, + overrides?: { + getResponse?: UserStorageResponseData[]; + getStatusCode?: number; + putStatusCode?: number; + deleteStatusCode?: number; + }, + ) => { + const previouslySetupPath = this.paths.get(path); + + this.paths.set(path, { + response: overrides?.getResponse || previouslySetupPath?.response || [], + server, + }); + + this.paths + .get(path) + ?.server.forGet(pathRegexps[path]) + .always() + .thenCallback((request) => + this.onGet(path, request, overrides?.getStatusCode), + ); + this.paths + .get(path) + ?.server.forPut(pathRegexps[path]) + .always() + .thenCallback((request) => + this.onPut(path, request, overrides?.putStatusCode), + ); + this.paths + .get(path) + ?.server.forDelete(pathRegexps[path]) + .always() + .thenCallback((request) => + this.onDelete(path, request, overrides?.deleteStatusCode), + ); + }; +} diff --git a/test/e2e/tests/metrics/nft-detection-metrics.spec.js b/test/e2e/tests/metrics/nft-detection-metrics.spec.js index 3c77fdb66731..a0c901087425 100644 --- a/test/e2e/tests/metrics/nft-detection-metrics.spec.js +++ b/test/e2e/tests/metrics/nft-detection-metrics.spec.js @@ -101,7 +101,7 @@ describe('Nft detection event @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'fullscreen', - is_profile_syncing_enabled: null, + is_profile_syncing_enabled: true, }); assert.deepStrictEqual(events[2].properties, { nft_autodetection_enabled: true, diff --git a/test/e2e/tests/metrics/token-detection-metrics.spec.js b/test/e2e/tests/metrics/token-detection-metrics.spec.js index 669aff0a9290..923f7c86a242 100644 --- a/test/e2e/tests/metrics/token-detection-metrics.spec.js +++ b/test/e2e/tests/metrics/token-detection-metrics.spec.js @@ -98,7 +98,7 @@ describe('Token detection event @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'fullscreen', - is_profile_syncing_enabled: null, + is_profile_syncing_enabled: true, }); assert.deepStrictEqual(events[2].properties, { token_detection_enabled: true, diff --git a/test/e2e/tests/metrics/wallet-created.spec.js b/test/e2e/tests/metrics/wallet-created.spec.js index 890ac9342a8a..fbe80fb595dc 100644 --- a/test/e2e/tests/metrics/wallet-created.spec.js +++ b/test/e2e/tests/metrics/wallet-created.spec.js @@ -87,7 +87,7 @@ describe('Wallet Created Events @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'fullscreen', - is_profile_syncing_enabled: null, + is_profile_syncing_enabled: true, }); }, ); diff --git a/test/e2e/tests/notifications/account-syncing/helpers.ts b/test/e2e/tests/notifications/account-syncing/helpers.ts new file mode 100644 index 000000000000..e54a5f6f96ae --- /dev/null +++ b/test/e2e/tests/notifications/account-syncing/helpers.ts @@ -0,0 +1,3 @@ +import { isManifestV3 } from '../../../../../shared/modules/mv3.utils'; + +export const IS_ACCOUNT_SYNCING_ENABLED = isManifestV3; diff --git a/test/e2e/tests/notifications/account-syncing/mockData.ts b/test/e2e/tests/notifications/account-syncing/mockData.ts new file mode 100644 index 000000000000..96e92ecd8491 --- /dev/null +++ b/test/e2e/tests/notifications/account-syncing/mockData.ts @@ -0,0 +1,12 @@ +export const accountsSyncMockResponse = [ + { + HashedKey: + '997050281e559a2bb40d1c2e73d9f0887cbea1b81ff9dd7815917949e37f4f2f', + Data: '{"v":"1","t":"scrypt","d":"1yC/ZXarV57HbqEZ46nH0JWgXfPl86nTHD7kai2g5gm290FM9tw5QjOaAAwIuQESEE8TIM/J9pIj7nmlGi+BZrevTtK3DXWXwnUQsCP7amKd5Q4gs3EEQgXpA0W+WJUgyElj869rwIv/C6tl5E2pK4j/0EAjMSIm1TGoj9FPohyRgZsOIt8VhZfb7w0GODsjPwPIkN6zazvJ3gAFYFPh7yRtebFs86z3fzqCWZ9zakdCHntchC2oZiaApXR9yzaPlGgnPg==","o":{"N":131072,"r":8,"p":1,"dkLen":16},"saltLen":16}', + }, + { + HashedKey: + 'e53d8feb65b4cf0c339e57bee2a81b155e056622f9192c54b707f928c8a42a7a', + Data: '{"v":"1","t":"scrypt","d":"O7QEtUo7q/jG+UNkD/HOxQARGGRXsGPrLsDlkwDfgfoYlPI0To/M3pJRBlKD0RLEFIPHtHBEA5bv/2izB21VljvhMnhHfo0KgQ+e8Uq1t7grwa+r+ge3qbPNY+w78Xt8GtC+Hkrw5fORKvCn+xjzaCHYV6RxKYbp1TpyCJq7hDrr1XiyL8kqbpE0hAHALrrQOoV9/WXJi9pC5J118kquXx8CNA1P5wO/BXKp1AbryGR6kVW3lsp1sy3lYE/TApa5lTj+","o":{"N":131072,"r":8,"p":1,"dkLen":16},"saltLen":16}', + }, +]; diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-adding-custom-name-account.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-after-adding-custom-name-account.spec.ts new file mode 100644 index 000000000000..6ca7c501fc84 --- /dev/null +++ b/test/e2e/tests/notifications/account-syncing/sync-after-adding-custom-name-account.spec.ts @@ -0,0 +1,120 @@ +import { Mockttp } from 'mockttp'; +import { + withFixtures, + defaultGanacheOptions, + completeImportSRPOnboardingFlow, +} from '../../../helpers'; +import FixtureBuilder from '../../../fixture-builder'; +import { mockNotificationServices } from '../mocks'; +import { + NOTIFICATIONS_TEAM_PASSWORD, + NOTIFICATIONS_TEAM_SEED_PHRASE, +} from '../constants'; +import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { accountsSyncMockResponse } from './mockData'; +import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; + +describe('Account syncing @no-mmi', function () { + if (!IS_ACCOUNT_SYNCING_ENABLED) { + return; + } + describe('from inside MetaMask', function () { + it('syncs newly added accounts', async function () { + const userStorageMockttpController = new UserStorageMockttpController(); + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: (server: Mockttp) => { + userStorageMockttpController.setupPath('accounts', server, { + getResponse: accountsSyncMockResponse, + }); + + return mockNotificationServices( + server, + userStorageMockttpController, + ); + }, + }, + async ({ driver }) => { + await driver.navigate(); + await completeImportSRPOnboardingFlow( + driver, + NOTIFICATIONS_TEAM_SEED_PHRASE, + NOTIFICATIONS_TEAM_PASSWORD, + ); + + await driver.clickElement('[data-testid="account-menu-icon"]'); + + await driver.wait(async () => { + const internalAccounts = await driver.findElements( + '.multichain-account-list-item', + ); + return internalAccounts.length === accountsSyncMockResponse.length; + }, 20000); + + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-add-account"]', + ); + await driver.fill('#account-name', 'My third account'); + + await driver.clickElementAndWaitToDisappear( + '[data-testid="submit-add-account-with-name"]', + ); + }, + ); + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: (server: Mockttp) => { + userStorageMockttpController.setupPath('accounts', server); + return mockNotificationServices( + server, + userStorageMockttpController, + ); + }, + }, + async ({ driver }) => { + await driver.navigate(); + await completeImportSRPOnboardingFlow( + driver, + NOTIFICATIONS_TEAM_SEED_PHRASE, + NOTIFICATIONS_TEAM_PASSWORD, + ); + + await driver.clickElement('[data-testid="account-menu-icon"]'); + + await driver.wait(async () => { + const internalAccounts = await driver.findElements( + '.multichain-account-list-item', + ); + return ( + internalAccounts.length === + userStorageMockttpController.paths.get('accounts')?.response + .length + ); + }, 20000); + + await driver.wait(async () => { + const internalAccounts = await driver.findElements( + '.multichain-account-list-item .multichain-account-list-item__account-name', + ); + const lastAccountName = await internalAccounts[ + internalAccounts.length - 1 + ].getText(); + + return lastAccountName === 'My third account'; + }, 20000); + }, + ); + }); + }); +}); diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts new file mode 100644 index 000000000000..1a55e38c713d --- /dev/null +++ b/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts @@ -0,0 +1,60 @@ +import { Mockttp } from 'mockttp'; +import { + withFixtures, + defaultGanacheOptions, + completeImportSRPOnboardingFlow, +} from '../../../helpers'; +import FixtureBuilder from '../../../fixture-builder'; +import { mockNotificationServices } from '../mocks'; +import { + NOTIFICATIONS_TEAM_PASSWORD, + NOTIFICATIONS_TEAM_SEED_PHRASE, +} from '../constants'; +import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import { accountsSyncMockResponse } from './mockData'; +import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; + +describe('Account syncing @no-mmi', function () { + if (!IS_ACCOUNT_SYNCING_ENABLED) { + return; + } + describe('from inside MetaMask', function () { + it('retrieves all previously synced accounts', async function () { + const userStorageMockttpController = new UserStorageMockttpController(); + + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }).build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: (server: Mockttp) => { + userStorageMockttpController.setupPath('accounts', server, { + getResponse: accountsSyncMockResponse, + }); + return mockNotificationServices( + server, + userStorageMockttpController, + ); + }, + }, + async ({ driver }) => { + await driver.navigate(); + await completeImportSRPOnboardingFlow( + driver, + NOTIFICATIONS_TEAM_SEED_PHRASE, + NOTIFICATIONS_TEAM_PASSWORD, + ); + + await driver.clickElement('[data-testid="account-menu-icon"]'); + + await driver.wait(async () => { + const internalAccounts = await driver.findElements( + '.multichain-account-list-item', + ); + return internalAccounts.length === accountsSyncMockResponse.length; + }, 20000); + }, + ); + }); + }); +}); diff --git a/test/e2e/tests/notifications/constants.ts b/test/e2e/tests/notifications/constants.ts new file mode 100644 index 000000000000..8c611dcd4f44 --- /dev/null +++ b/test/e2e/tests/notifications/constants.ts @@ -0,0 +1,7 @@ +// As we rely on profile syncing for most of our features, we need to use the same SRP for all of our tests +export const NOTIFICATIONS_TEAM_SEED_PHRASE = + 'leisure swallow trip elbow prison wait rely keep supply hole general mountain'; +export const NOTIFICATIONS_TEAM_PASSWORD = 'notify_password'; +// You can use the storage key below to generate mock data +export const NOTIFICATIONS_TEAM_STORAGE_KEY = + '0d55d30da233959674d14076737198c05ae3fb8631a17e20d3c28c60dddd82f7'; diff --git a/test/e2e/tests/notifications/mocks.ts b/test/e2e/tests/notifications/mocks.ts index 9161faf2967f..2bd6847a0e72 100644 --- a/test/e2e/tests/notifications/mocks.ts +++ b/test/e2e/tests/notifications/mocks.ts @@ -1,15 +1,12 @@ import { Mockttp, RequestRuleBuilder } from 'mockttp'; -import { - AuthenticationController, - UserStorageController, -} from '@metamask/profile-sync-controller'; +import { AuthenticationController } from '@metamask/profile-sync-controller'; import { NotificationServicesController, NotificationServicesPushController, } from '@metamask/notification-services-controller'; +import { UserStorageMockttpController } from '../../helpers/user-storage/userStorageMockttpController'; const AuthMocks = AuthenticationController.Mocks; -const StorageMocks = UserStorageController.Mocks; const NotificationMocks = NotificationServicesController.Mocks; const PushMocks = NotificationServicesPushController.Mocks; @@ -20,32 +17,30 @@ type MockResponse = { }; /** - * E2E mock setup for notification APIs (Auth, Storage, Notifications, Push Notifications, Profile syncing) + * E2E mock setup for notification APIs (Auth, UserStorage, Notifications, Push Notifications, Profile syncing) * * @param server - server obj used to mock our endpoints + * @param userStorageMockttpController - optional controller to mock user storage endpoints */ -export async function mockNotificationServices(server: Mockttp) { +export async function mockNotificationServices( + server: Mockttp, + userStorageMockttpController?: UserStorageMockttpController, +) { // Auth mockAPICall(server, AuthMocks.getMockAuthNonceResponse()); mockAPICall(server, AuthMocks.getMockAuthLoginResponse()); mockAPICall(server, AuthMocks.getMockAuthAccessTokenResponse()); // Storage - mockAPICall(server, await StorageMocks.getMockUserStorageGetResponse()); - mockAPICall(server, await StorageMocks.getMockUserStoragePutResponse()); - - // TODO - add better mock responses for other Profile Sync features - // (Account Sync, Network Sync, ...) - server - .forGet(/https:\/\/user-storage\.api\.cx\.metamask\.io\/.*/gu) - ?.thenCallback(() => ({ - statusCode: 404, - })); - server - .forPut(/https:\/\/user-storage\.api\.cx\.metamask\.io\/.*/gu) - ?.thenCallback(() => ({ - statusCode: 204, - })); + if (!userStorageMockttpController?.paths.get('accounts')) { + new UserStorageMockttpController().setupPath('accounts', server); + } + if (!userStorageMockttpController?.paths.get('networks')) { + new UserStorageMockttpController().setupPath('networks', server); + } + if (!userStorageMockttpController?.paths.get('notifications')) { + new UserStorageMockttpController().setupPath('notifications', server); + } // Notifications mockAPICall(server, NotificationMocks.getMockFeatureAnnouncementResponse());