From e5ba00b798e165594cce5ae88e6e2d384fce9f7e Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Wed, 20 Apr 2022 12:35:53 -0700 Subject: [PATCH] Initial GV2 mock tests --- .github/workflows/ci.yml | 6 +- package.json | 2 +- ts/SignalProtocolStore.ts | 8 +- ts/test-mock/bootstrap.ts | 2 + ts/test-mock/gv2/create_test.ts | 175 +++++++++++++++++++++++++++++++ ts/test-mock/storage/fixtures.ts | 2 +- yarn.lock | 8 +- 7 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 ts/test-mock/gv2/create_test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5e30c7e0..8718926fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,7 +157,7 @@ jobs: env: SIGNAL_ENV: production - storage-service: + mock-tests: needs: lint runs-on: ubuntu-latest if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' }} @@ -198,11 +198,11 @@ jobs: - name: Bundle run: yarn build:webpack - - name: Run storage service tests + - name: Run mock server tests run: | set -o pipefail xvfb-run --auto-servernum yarn test-mock timeout-minutes: 10 env: NODE_ENV: production - DEBUG: mock:test-storage + DEBUG: mock:test:* diff --git a/package.json b/package.json index 7f1230294..a20236036 100644 --- a/package.json +++ b/package.json @@ -189,7 +189,7 @@ "@chanzuckerberg/axe-storybook-testing": "3.0.2", "@electron/fuses": "1.5.0", "@mixer/parallel-prettier": "2.0.1", - "@signalapp/mock-server": "1.3.0", + "@signalapp/mock-server": "1.5.0-rc.3", "@storybook/addon-actions": "5.1.11", "@storybook/addon-knobs": "5.1.11", "@storybook/addons": "5.1.11", diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts index 5ba1698b5..c4d887c2f 100644 --- a/ts/SignalProtocolStore.ts +++ b/ts/SignalProtocolStore.ts @@ -45,7 +45,7 @@ import type { } from './textsecure/Types.d'; import type { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; import type { UUIDStringType } from './types/UUID'; -import { UUID } from './types/UUID'; +import { UUID, UUIDKind } from './types/UUID'; import type { Address } from './types/Address'; import type { QualifiedAddressStringType } from './types/QualifiedAddress'; import { QualifiedAddress } from './types/QualifiedAddress'; @@ -1682,7 +1682,11 @@ export class SignalProtocolStore extends EventsMixin { const identityRecord = await this.getOrMigrateIdentityRecord(uuid); const id = uuid.toString(); - window.ConversationController.getOrCreate(id, 'private'); + // When saving a PNI identity - don't create a separate conversation + const uuidKind = window.textsecure.storage.user.getOurUuidKind(uuid); + if (uuidKind !== UUIDKind.PNI) { + window.ConversationController.getOrCreate(id, 'private'); + } const updates: Partial = { ...identityRecord, diff --git a/ts/test-mock/bootstrap.ts b/ts/test-mock/bootstrap.ts index 259a09e96..acc836812 100644 --- a/ts/test-mock/bootstrap.ts +++ b/ts/test-mock/bootstrap.ts @@ -13,6 +13,8 @@ import { MAX_READ_KEYS as MAX_STORAGE_READ_KEYS } from '../services/storageConst import * as durations from '../util/durations'; import { App } from './playwright'; +export { App }; + const debug = createDebug('mock:bootstrap'); const ELECTRON = path.join( diff --git a/ts/test-mock/gv2/create_test.ts b/ts/test-mock/gv2/create_test.ts new file mode 100644 index 000000000..6a77b1007 --- /dev/null +++ b/ts/test-mock/gv2/create_test.ts @@ -0,0 +1,175 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; +import type { PrimaryDevice, Group } from '@signalapp/mock-server'; +import { StorageState, Proto } from '@signalapp/mock-server'; +import createDebug from 'debug'; + +import * as durations from '../../util/durations'; +import { Bootstrap } from '../bootstrap'; +import type { App } from '../bootstrap'; + +export const debug = createDebug('mock:test:gv2'); + +describe('gv2', function needsName() { + this.timeout(durations.MINUTE); + + let bootstrap: Bootstrap; + let app: App; + let pniContact: PrimaryDevice; + + beforeEach(async () => { + bootstrap = new Bootstrap(); + await bootstrap.init(); + + const { phone, contacts, server } = bootstrap; + + let state = StorageState.getEmpty(); + + state = state.updateAccount({ + profileKey: phone.profileKey.serialize(), + e164: phone.device.number, + }); + + const [first] = contacts; + state = state.addContact(first, { + identityState: Proto.ContactRecord.IdentityState.VERIFIED, + whitelisted: true, + + identityKey: first.publicKey.serialize(), + profileKey: first.profileKey.serialize(), + }); + + pniContact = await server.createPrimaryDevice({ + profileName: 'My name is PNI', + }); + state = state.addContact(pniContact, { + identityState: Proto.ContactRecord.IdentityState.VERIFIED, + whitelisted: true, + + identityKey: pniContact.pniPublicKey.serialize(), + + // Give PNI as the uuid! + serviceUuid: pniContact.device.pni, + givenName: 'PNI Contact', + }); + + await phone.setStorageState(state); + + app = await bootstrap.link(); + }); + + afterEach(async () => { + await app.close(); + await bootstrap.teardown(); + }); + + it('should create group and modify it', async () => { + const { phone, contacts } = bootstrap; + const [first] = contacts; + + let state = await phone.expectStorageState('initial state'); + + const window = await app.getWindow(); + + const leftPane = window.locator('.left-pane-wrapper'); + const conversationStack = window.locator('.conversation-stack'); + + debug('clicking compose and "New group" buttons'); + + await leftPane.locator('.module-main-header__compose-icon').click(); + + await leftPane + .locator('_react=BaseConversationListItem[title = "New group"]') + .click(); + + debug('inviting ACI member'); + + await leftPane + .locator( + '_react=BaseConversationListItem' + + `[title = ${JSON.stringify(first.profileName)}]` + ) + .click(); + + debug('inviting PNI member'); + + await leftPane + .locator('.module-left-pane__compose-search-form__input') + .type('PNI'); + + await leftPane + .locator('_react=BaseConversationListItem[title = "PNI Contact"]') + .click(); + + await leftPane + .locator('.module-left-pane__footer button >> "Next"') + .click(); + + debug('entering group title'); + + await leftPane.type('My group'); + + await leftPane + .locator('.module-left-pane__footer button >> "Create"') + .click(); + + debug('waiting for invitation modal'); + + { + const modal = window.locator( + '.module-GroupDialog:has-text("Invitation sent")' + ); + + await modal.locator('button >> "Okay"').click(); + } + + debug('waiting for group data from storage service'); + + let group: Group; + { + state = await phone.waitForStorageState({ after: state }); + + const groups = await phone.getAllGroups(state); + assert.strictEqual(groups.length, 1); + + [group] = groups; + assert.strictEqual(group.title, 'My group'); + assert.strictEqual(group.revision, 0); + assert.strictEqual(group.state.members?.length, 2); + assert.strictEqual(group.state.membersPendingProfileKey?.length, 1); + } + + debug('opening group settings'); + + await conversationStack + .locator('button.module-ConversationHeader__button--more') + .click(); + + await conversationStack + .locator('.react-contextmenu-item >> "Group settings"') + .click(); + + debug('editing group title'); + { + const detailsHeader = conversationStack.locator( + '_react=ConversationDetailsHeader' + ); + detailsHeader.locator('button >> "My group"').click(); + + const modal = window.locator('.module-Modal:has-text("Edit group")'); + + // Group title should be immediately focused. + await modal.type(' (v2)'); + + await modal.locator('button >> "Save"').click(); + } + + debug('waiting for the second group update'); + group = await phone.waitForGroupUpdate(group); + + assert.strictEqual(group.title, 'My group (v2)'); + assert.strictEqual(group.revision, 1); + }); +}); diff --git a/ts/test-mock/storage/fixtures.ts b/ts/test-mock/storage/fixtures.ts index 1badb6396..64011835a 100644 --- a/ts/test-mock/storage/fixtures.ts +++ b/ts/test-mock/storage/fixtures.ts @@ -8,7 +8,7 @@ import { App } from '../playwright'; import { Bootstrap } from '../bootstrap'; import type { BootstrapOptions } from '../bootstrap'; -export const debug = createDebug('mock:test-storage'); +export const debug = createDebug('mock:test:storage'); export { App, Bootstrap }; diff --git a/yarn.lock b/yarn.lock index 9d8f3fec0..1a75f1140 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1372,10 +1372,10 @@ node-gyp-build "^4.2.3" uuid "^8.3.0" -"@signalapp/mock-server@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-1.3.0.tgz#288a994c4f5c26c4c2680289af471e08746ac353" - integrity sha512-ix3GO0lytE02nWLj1fKY3UhKM3lCynhvF2LVNHEiMen9wurVyb8mVcmBDb9zRBi63tZmFLAq/IQEYrc1OK3ZJQ== +"@signalapp/mock-server@1.5.0-rc.3": + version "1.5.0-rc.3" + resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-1.5.0-rc.3.tgz#2bb0d80555e84740bf7c110386da6ec9200785d5" + integrity sha512-ShoMNL4XHtvBXDrzgQD2xXpqTDjzzVegQjlA15c9H285Z1qQJhQfnu3A13YDmAYqZ0LbXM8Re5AnWtZJYdGSCA== dependencies: "@signalapp/libsignal-client" "0.15.0" debug "^4.3.2"