diff --git a/extension/chrome/elements/compose-modules/compose-draft-module.ts b/extension/chrome/elements/compose-modules/compose-draft-module.ts index 09cc59769db..68c38132da9 100644 --- a/extension/chrome/elements/compose-modules/compose-draft-module.ts +++ b/extension/chrome/elements/compose-modules/compose-draft-module.ts @@ -126,7 +126,7 @@ export class ComposeDraftModule extends ViewModule { const msgData = await this.view.inputModule.extractAll(); const { pubkeys } = await this.view.storageModule.collectSingleFamilyKeys([], msgData.from.email, true); msgData.pwd = undefined; // not needed for drafts - const sendable = await new EncryptedMsgMailFormatter(this.view, true).sendableNonPwdMsg(msgData, pubkeys); + const sendable = await new EncryptedMsgMailFormatter(this.view, true).encryptSendableNonPwdMsg(msgData, pubkeys); if (this.view.replyParams?.inReplyTo) { sendable.headers.References = this.view.replyParams.inReplyTo; sendable.headers['In-Reply-To'] = this.view.replyParams.inReplyTo; diff --git a/extension/chrome/elements/compose-modules/formatters/encrypted-mail-msg-formatter.ts b/extension/chrome/elements/compose-modules/formatters/encrypted-mail-msg-formatter.ts index ff506a15b08..1a326b31b65 100644 --- a/extension/chrome/elements/compose-modules/formatters/encrypted-mail-msg-formatter.ts +++ b/extension/chrome/elements/compose-modules/formatters/encrypted-mail-msg-formatter.ts @@ -6,7 +6,7 @@ import { BaseMailFormatter } from './base-mail-formatter.js'; import { ComposerResetBtnTrigger } from '../compose-err-module.js'; import { Mime, SendableMsgBody } from '../../../../js/common/core/mime.js'; import { getUniqueRecipientEmails, NewMsgData } from '../compose-types.js'; -import { EmailParts, Str, Value } from '../../../../js/common/core/common.js'; +import { Str, Value } from '../../../../js/common/core/common.js'; import { ApiErr } from '../../../../js/common/api/shared/api-error.js'; import { Attachment } from '../../../../js/common/core/attachment.js'; import { Buf } from '../../../../js/common/core/buf.js'; @@ -21,10 +21,9 @@ import { Xss } from '../../../../js/common/platform/xss.js'; import { AcctStore } from '../../../../js/common/platform/store/acct-store.js'; import { SmimeKey } from '../../../../js/common/core/crypto/smime/smime-key.js'; import { PgpHash } from '../../../../js/common/core/crypto/pgp/pgp-hash.js'; -import { UploadedMessageData } from '../../../../js/common/api/account-server.js'; +import { UploadedMessageResponse } from '../../../../js/common/api/account-server.js'; import { ParsedKeyInfo } from '../../../../js/common/core/crypto/key-store-util.js'; import { MultipleMessages } from './general-mail-formatter.js'; -import { Api, RecipientType } from '../../../../js/common/api/shared/api.js'; /** * this type must be kept in sync with FES UI code, changes must be backwards compatible @@ -44,9 +43,9 @@ type ReplyInfoRaw = { export class EncryptedMsgMailFormatter extends BaseMailFormatter { public sendableMsgs = async (newMsg: NewMsgData, pubkeys: PubkeyResult[], signingKey?: ParsedKeyInfo): Promise => { if (newMsg.pwd && !this.isDraft) { - return await this.formatSendablePwdMsgs(newMsg, pubkeys, signingKey); + return await this.formatSendablePwdMsg(newMsg, pubkeys, signingKey); } else { - const msg = await this.sendableNonPwdMsg(newMsg, pubkeys, signingKey?.key); + const msg = await this.encryptSendableNonPwdMsg(newMsg, pubkeys, signingKey?.key); return { senderKi: signingKey?.keyInfo, msgs: [msg], @@ -58,7 +57,7 @@ export class EncryptedMsgMailFormatter extends BaseMailFormatter { } }; - public sendableNonPwdMsg = async (newMsg: NewMsgData, pubkeys: PubkeyResult[], signingPrv?: Key): Promise => { + public encryptSendableNonPwdMsg = async (newMsg: NewMsgData, pubkeys: PubkeyResult[], signingPrv?: Key): Promise => { if (!this.isDraft) { // S/MIME drafts are currently formatted with inline armored data const x509certs = pubkeys.map(entry => entry.pubkey).filter(pub => pub.family === 'x509'); @@ -92,70 +91,42 @@ export class EncryptedMsgMailFormatter extends BaseMailFormatter { }); }; - private formatSendablePwdMsgs = async (newMsg: NewMsgData, pubkeys: PubkeyResult[], signingKey?: ParsedKeyInfo) => { + private formatSendablePwdMsg = async (newMsg: NewMsgData, pubkeys: PubkeyResult[], signingKey?: ParsedKeyInfo) => { // password-protected message, temporarily uploaded (already encrypted) to: // - flowcrypt.com/api (consumers and customers without on-prem setup), or - // - FlowCrypt Enterprise Server (enterprise customers with on-prem setup) + // - FlowCrypt External Service at fes.example.com (enterprise customers with on-prem setup) // It will be served to recipient through web - const uploadedMessageData = await this.prepareAndUploadPwdEncryptedMsg(newMsg); // encrypted for pwd only, pubkeys ignored - // pwdRecipients that have their personal link - const individualPwdRecipients = Object.keys(uploadedMessageData.emailToExternalIdAndUrl ?? {}).filter(email => !pubkeys.some(p => p.email === email)); - const legacyPwdRecipients: { [type in RecipientType]?: EmailParts[] } = {}; + const uploadedMessageResponse = await this.prepareEncryptAndUploadPwdEncryptedMsg(newMsg); // encrypted for pwd only, pubkeys ignored newMsg.pwd = undefined; - const encryptedAttachments = await this.view.attachmentsModule.attachment.collectEncryptAttachments(pubkeys); - const pubkeyRecipients: { [type in RecipientType]?: EmailParts[] } = {}; - for (const [sendingType, value] of Object.entries(newMsg.recipients)) { - if (Api.isRecipientHeaderNameType(sendingType)) { - pubkeyRecipients[sendingType] = value?.filter(emailPart => pubkeys.some(p => p.email === emailPart.email)); - legacyPwdRecipients[sendingType] = value?.filter( - emailPart => !pubkeys.some(p => p.email === emailPart.email) && !individualPwdRecipients.includes(emailPart.email) - ); - } - } - const msgs: SendableMsg[] = []; - // pubkey recipients get one combined message. If there are not pubkey recpients, only password - protected messages will be sent - if (pubkeyRecipients.to?.length || pubkeyRecipients.cc?.length || pubkeyRecipients.bcc?.length) { - const uniquePubkeyRecipientToAndCCs = Value.arr.unique( - (pubkeyRecipients.to || []).concat(pubkeyRecipients.cc || []).map(recipient => recipient.email.toLowerCase()) - ); - // pubkey recipients should be able to reply to "to" and "cc" pwd recipients - const replyToForMessageSentToPubkeyRecipients = (newMsg.recipients.to ?? []) - .concat(newMsg.recipients.cc ?? []) - .filter(recipient => !uniquePubkeyRecipientToAndCCs.includes(recipient.email.toLowerCase())); - const pubkeyMsgData = { - ...newMsg, - recipients: pubkeyRecipients, - // brackets are required for test emails like '@test:8001' - replyTo: replyToForMessageSentToPubkeyRecipients.length - ? `${Str.formatEmailList([newMsg.from, ...replyToForMessageSentToPubkeyRecipients], true)}` - : undefined, - }; - msgs.push(await this.sendableNonPwdMsg(pubkeyMsgData, pubkeys, signingKey?.key)); - } - // adding individual messages for each recipient that doesn't have a pubkey - for (const recipientEmail of individualPwdRecipients) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { url, externalId } = uploadedMessageData.emailToExternalIdAndUrl![recipientEmail]; - const foundParsedRecipient = (newMsg.recipients.to ?? []) - .concat(newMsg.recipients.cc ?? []) - .concat(newMsg.recipients.bcc ?? []) - .find(r => r.email.toLowerCase() === recipientEmail.toLowerCase()); - // todo: since a message is allowed to have only `cc` or `bcc` without `to`, should we preserve the original placement(s) of the recipient? - const individualMsgData = { ...newMsg, recipients: { to: [foundParsedRecipient ?? { email: recipientEmail }] } }; - msgs.push(await this.sendablePwdMsg(individualMsgData, pubkeys, { msgUrl: url, externalId }, signingKey?.key)); - } - if (legacyPwdRecipients.to?.length || legacyPwdRecipients.cc?.length || legacyPwdRecipients.bcc?.length) { - const legacyPwdMsgData = { ...newMsg, recipients: legacyPwdRecipients }; - msgs.push(await this.sendablePwdMsg(legacyPwdMsgData, pubkeys, { msgUrl: uploadedMessageData.url }, signingKey?.key)); - } + // We used to support sending individual messages to each of the password recipients, each with unique link + // as per https://github.com/FlowCrypt/flowcrypt-browser/issues/4348. We later reverted that behavior in + // https://github.com/FlowCrypt/flowcrypt-browser/issues/4870. Therefore currently it's a single message + // again even though the code could support multiple messages. If by 2024 there is still support for multiple + // messages that is unused, then this can be refactored back to a single message + // (`SendableMsg[]` to `SendableMsg` and so on, including error handling which is much simpler when there is + // just one message to send) + const msg = await this.sendableCombinedPubkeyMsgWithoutAttachedFilesWithLinkToUploadedPwdMsg( + newMsg, + pubkeys, + { msgUrl: uploadedMessageResponse.url, externalId: uploadedMessageResponse.externalId }, + signingKey?.key + ); + // the above message has pgp/mime encrypted content that was attached as a set of pgp/mime attachments, + // but doesn't have the actual attachments the user has attached (they were uploaded to FES/backend but weren't + // attached to the message itself). We are adding the attachments here. + const pubkeyEncryptedAttachments = await this.view.attachmentsModule.attachment.collectEncryptAttachments(pubkeys); + msg.attachments = msg.attachments.concat(pubkeyEncryptedAttachments); return { senderKi: signingKey?.keyInfo, - msgs, - renderSentMessage: { recipients: newMsg.recipients, attachments: encryptedAttachments }, + msgs: [msg], + renderSentMessage: { + recipients: newMsg.recipients, + attachments: pubkeyEncryptedAttachments + }, }; }; - private prepareAndUploadPwdEncryptedMsg = async (newMsg: NewMsgData): Promise => { + private prepareEncryptAndUploadPwdEncryptedMsg = async (newMsg: NewMsgData): Promise => { // PGP/MIME + included attachments (encrypted for password only) if (!newMsg.pwd) { throw new Error('password unexpectedly missing'); @@ -192,17 +163,19 @@ export class EncryptedMsgMailFormatter extends BaseMailFormatter { { Subject: newMsg.subject }, // eslint-disable-line @typescript-eslint/naming-convention await this.view.attachmentsModule.attachment.collectAttachments() ); - const { data: pwdEncryptedWithAttachments } = await this.encryptDataArmor(Buf.fromUtfStr(pgpMimeWithAttachments), newMsg.pwd, []); // encrypted only for pwd, not signed + // encrypted only for pwd, not signed + const { data: pwdEncryptedWithAttachments } = await this.encryptDataArmor(Buf.fromUtfStr(pgpMimeWithAttachments), newMsg.pwd, []); return await this.view.acctServer.messageUpload( pwdEncryptedWithAttachments, replyToken, newMsg.from.email, // todo: Str.formatEmailWithOptionalName? newMsg.recipients, - p => this.view.sendBtnModule.renderUploadProgress(p, 'FIRST-HALF') // still need to upload to Gmail later, this request represents first half of progress + // still need to upload to Gmail later, this request represents first half of progress + p => this.view.sendBtnModule.renderUploadProgress(p, 'FIRST-HALF') ); }; - private sendablePwdMsg = async ( + private sendableCombinedPubkeyMsgWithoutAttachedFilesWithLinkToUploadedPwdMsg = async ( newMsg: NewMsgData, pubs: PubkeyResult[], { msgUrl, externalId }: { msgUrl: string; externalId?: string }, diff --git a/extension/js/common/api/account-server.ts b/extension/js/common/api/account-server.ts index c575fe91e08..6098cb36191 100644 --- a/extension/js/common/api/account-server.ts +++ b/extension/js/common/api/account-server.ts @@ -11,10 +11,9 @@ import { ParsedRecipients } from './email-provider/email-provider-api.js'; import { BackendAuthErr } from './shared/api-error.js'; import { Api, ProgressCb } from './shared/api.js'; -export type UploadedMessageData = { +export type UploadedMessageResponse = { url: string; // both FES and FlowCryptComApi - externalId?: string; // legacy FES - emailToExternalIdAndUrl?: { [email: string]: { url: string; externalId: string } }; // FES only + externalId?: string; // FES }; /** @@ -59,7 +58,7 @@ export class AccountServer extends Api { from: string, recipients: ParsedRecipients, progressCb: ProgressCb - ): Promise => { + ): Promise => { if (await this.isFesUsed()) { const fes = new EnterpriseServer(this.acctEmail); // Recipients are used to later cross-check replies from the web diff --git a/extension/js/common/api/account-servers/enterprise-server.ts b/extension/js/common/api/account-servers/enterprise-server.ts index 5a28ecf1260..d27a8f8f876 100644 --- a/extension/js/common/api/account-servers/enterprise-server.ts +++ b/extension/js/common/api/account-servers/enterprise-server.ts @@ -20,9 +20,8 @@ type EventTag = 'compose' | 'decrypt' | 'setup' | 'settings' | 'import-pub' | 'i export namespace FesRes { export type ReplyToken = { replyToken: string }; export type MessageUpload = { - url: string; // LEGACY - externalId: string; // LEGACY - emailToExternalIdAndUrl?: { [email: string]: { url: string; externalId: string } }; + url: string; + externalId: string; }; export type ServiceInfo = { vendor: string; service: string; orgId: string; version: string; apiVersion: string }; export type ClientConfiguration = { clientConfiguration: ClientConfigurationJson }; diff --git a/test/source/mock/fes/fes-endpoints.ts b/test/source/mock/fes/fes-endpoints.ts index 6063725bc58..f408bf562ec 100644 --- a/test/source/mock/fes/fes-endpoints.ts +++ b/test/source/mock/fes/fes-endpoints.ts @@ -34,21 +34,11 @@ const processMessageFromUser = async (body: string) => { // small.txt expect(decryptedMimeMsg).to.contain('Content-Type: text/plain; name=small.txt\r\n' + 'Content-Disposition: attachment; filename=small.txt'); expect(decryptedMimeMsg).to.contain('Content-Transfer-Encoding: base64\r\n\r\n' + 'c21hbGwgdGV4dCBmaWxlCm5vdCBtdWNoIGhlcmUKdGhpcyB3b3JrZWQK'); - const response = { + return { // this url is required for pubkey encrypted message url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-ID`, externalId: 'FES-MOCK-EXTERNAL-ID', - emailToExternalIdAndUrl: {} as { [email: string]: { url: string; externalId: string } }, }; - response.emailToExternalIdAndUrl['to@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-TO@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-TO@EXAMPLE.COM-ID', - }; - response.emailToExternalIdAndUrl['bcc@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-BCC@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-BCC@EXAMPLE.COM-ID', - }; - return response; }; const processMessageFromUser2 = async (body: string) => { @@ -76,29 +66,11 @@ const processMessageFromUser2 = async (body: string) => { expect(decryptedMimeMsg).to.contain( 'Content-Transfer-Encoding: base64\r\n\r\n' + 'JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURl' ); - const response = { + return { // this url is required for pubkey encrypted message url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-ID`, externalId: 'FES-MOCK-EXTERNAL-ID', - emailToExternalIdAndUrl: {} as { [email: string]: { url: string; externalId: string } }, - }; - response.emailToExternalIdAndUrl['to@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-TO@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-TO@EXAMPLE.COM-ID', - }; - response.emailToExternalIdAndUrl['sender@domain.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-SENDER@DOMAIN.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-SENDER@DOMAIN.COM-ID', - }; - response.emailToExternalIdAndUrl['flowcrypt.compatibility@gmail.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-FLOWCRYPT.COMPATIBILITY@GMAIL.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-FLOWCRYPT.COMPATIBILITY@GMAIL.COM-ID', - }; - response.emailToExternalIdAndUrl['mock.only.pubkey@flowcrypt.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-MOCK.ONLY.PUBKEY@FLOWCRYPT.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-MOCK.ONLY.PUBKEY@FLOWCRYPT.COM-ID', }; - return response; }; const processMessageFromUser3 = async (body: string) => { @@ -120,69 +92,15 @@ const processMessageFromUser3 = async (body: string) => { const decryptedMimeMsg = decrypted.content!.toUtfStr(); // small.txt expect(decryptedMimeMsg).to.contain( - 'Content-Type: text/plain\r\n' + 'Content-Transfer-Encoding: quoted-printable\r\n\r\n' + 'PWD encrypted message with FES - pubkey recipient in bcc' + 'Content-Type: text/plain\r\n' + + 'Content-Transfer-Encoding: quoted-printable\r\n\r\n' + + 'PWD encrypted message with FES - pubkey recipient in bcc' ); - const response = { + return { // this url is required for pubkey encrypted message url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-ID`, externalId: 'FES-MOCK-EXTERNAL-ID', - emailToExternalIdAndUrl: {} as { [email: string]: { url: string; externalId: string } }, }; - response.emailToExternalIdAndUrl['to@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-TO@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-TO@EXAMPLE.COM-ID', - }; - response.emailToExternalIdAndUrl['flowcrypt.compatibility@gmail.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-FLOWCRYPT.COMPATIBILITY@GMAIL.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-FLOWCRYPT.COMPATIBILITY@GMAIL.COM-ID', - }; - return response; -}; - -const processMessageFromUser4 = async (body: string) => { - const response = { - // this url is required for pubkey encrypted message - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-ID`, - externalId: 'FES-MOCK-EXTERNAL-ID', - emailToExternalIdAndUrl: {} as { [email: string]: { url: string; externalId: string } }, - }; - if (body.includes('to@example.com')) { - response.emailToExternalIdAndUrl['to@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-TO@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-TO@EXAMPLE.COM-ID', - }; - } - if (body.includes('invalid@example.com')) { - response.emailToExternalIdAndUrl['invalid@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-INVALID@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-INVALID@EXAMPLE.COM-ID', - }; - } - if (body.includes('timeout@example.com')) { - response.emailToExternalIdAndUrl['timeout@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-TIMEOUT@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-TIMEOUT@EXAMPLE.COM-ID', - }; - } - if (body.includes('Mr Cc ')) { - response.emailToExternalIdAndUrl['cc@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-CC@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-CC@EXAMPLE.COM-ID', - }; - } - if (body.includes('First Last ')) { - response.emailToExternalIdAndUrl['flowcrypt.compatibility@gmail.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-FLOWCRYPT.COMPATIBILITY@GMAIL.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-FLOWCRYPT.COMPATIBILITY@GMAIL.COM-ID', - }; - } - if (body.includes('gatewayfailure@example.com')) { - response.emailToExternalIdAndUrl['gatewayfailure@example.com'] = { - url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-FOR-GATEWAYFAILURE@EXAMPLE.COM-ID`, - externalId: 'FES-MOCK-EXTERNAL-FOR-GATEWAYFAILURE@EXAMPLE.COM-ID', - }; - } - return response; }; export const mockFesEndpoints: HandlersDefinition = { @@ -233,6 +151,13 @@ export const mockFesEndpoints: HandlersDefinition = { if (req.headers.host === standardFesUrl && req.method === 'POST' && typeof body === 'string') { // test: `compose - user@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal` authenticate(req, 'oidc'); + if (body.includes('gatewayfailure@example.com')) { // recipient + return { + // this url is required for pubkey encrypted message + url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-ID`, + externalId: 'FES-MOCK-EXTERNAL-FOR-GATEWAYFAILURE@EXAMPLE.COM-ID', + } + } if (body.includes('"from":"user@standardsubdomainfes.localhost:8001"')) { return await processMessageFromUser(body); } @@ -243,7 +168,11 @@ export const mockFesEndpoints: HandlersDefinition = { return await processMessageFromUser3(body); } if (body.includes('"from":"user4@standardsubdomainfes.localhost:8001"')) { - return await processMessageFromUser4(body); + return { + // this url is required for pubkey encrypted message + url: `http://${standardFesUrl}/message/FES-MOCK-MESSAGE-ID`, + externalId: 'FES-MOCK-EXTERNAL-ID', + } } } throw new HttpClientErr('Not Found', 404); @@ -288,7 +217,7 @@ export const mockFesEndpoints: HandlersDefinition = { throw new HttpClientErr('Not Found', 404); }, '/api/v1/message/FES-MOCK-EXTERNAL-FOR-GATEWAYFAILURE@EXAMPLE.COM-ID/gateway': async () => { - // test: `user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - a send fails with gateway update error` + // test: `user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - send fails with gateway update error` throw new HttpClientErr(`Test error`, Status.BAD_REQUEST); }, }; diff --git a/test/source/mock/google/google-data.ts b/test/source/mock/google/google-data.ts index b40894c24b4..e761bb7501f 100644 --- a/test/source/mock/google/google-data.ts +++ b/test/source/mock/google/google-data.ts @@ -282,7 +282,11 @@ export class GoogleData { payload: { headers: [ { name: 'Subject', value: parsedMail.subject || '' }, - { name: 'Message-ID', value: parsedMail.messageId || '' }, + // todo - Gmail doesn't retain message ids we give it in the MIME message. + // therefore ideally, we would not retain them in mock either, to be consistent + // in a separate PR try to remove `parsedMail.messageId || ` below + // see if it breaks any tests + { name: 'Message-ID', value: parsedMail.messageId || `mock-gmail-message-id-${Util.lousyRandom()}` }, ], body, }, diff --git a/test/source/mock/google/strategies/send-message-strategy.ts b/test/source/mock/google/strategies/send-message-strategy.ts index 272dcaf64c8..27dee6db6a8 100644 --- a/test/source/mock/google/strategies/send-message-strategy.ts +++ b/test/source/mock/google/strategies/send-message-strategy.ts @@ -28,27 +28,21 @@ class PwdAndPubkeyEncryptedMessagesWithFlowCryptComApiTestStrategy implements IT public test = async (parseResult: ParseMsgResult, id: string) => { const mimeMsg = parseResult.mimeMsg; const senderEmail = Str.parseEmail(mimeMsg.from!.text).email; - await new SaveMessageInStorageStrategy().test(parseResult, id); - if (mimeMsg.cc) { - // this is a message to the pubkey recipient - expect((mimeMsg.cc as AddressObject).text!).to.include('flowcrypt.compatibility@gmail.com'); - expect(mimeMsg.text!).to.not.include('has sent you a password-encrypted email'); - expect(mimeMsg.text!).to.not.include('Follow this link to open it'); - const kisWithPp = await Config.getKeyInfo(['flowcrypt.compatibility.1pp1', 'flowcrypt.compatibility.2pp1']); - const encryptedData = Buf.fromUtfStr(mimeMsg.text!); - const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData, verificationPubs: [] }); - expect(decrypted.success).to.be.true; - expect(decrypted.content!.toUtfStr()).to.contain('PWD and pubkey encrypted messages with flowcrypt.com/api'); - expect(mimeMsg.bcc).to.be.an.undefined; - expect(mimeMsg.to).to.be.an.undefined; - expect((mimeMsg.headers.get('reply-to') as AddressObject).text).to.equal('First Last , test@email.com'); - } else { - expect(mimeMsg.text!).to.contain(`${senderEmail} has sent you a password-encrypted email`); - expect(mimeMsg.text!).to.contain('Follow this link to open it'); - if (!mimeMsg.text?.match(/https:\/\/flowcrypt.com\/[a-z0-9A-Z]{10}/)) { - throw new HttpClientErr(`Error: cannot find pwd encrypted flowcrypt.com/api link in:\n\n${mimeMsg.text}`); - } + expect(mimeMsg.text!).to.contain(`${senderEmail} has sent you a password-encrypted email`); + expect(mimeMsg.text!).to.contain('Follow this link to open it'); + if (!mimeMsg.text?.match(/https:\/\/flowcrypt.com\/[a-z0-9A-Z]{10}/)) { + throw new HttpClientErr(`Error: cannot find pwd encrypted flowcrypt.com/api link in:\n\n${mimeMsg.text}`); } + const kisWithPp = await Config.getKeyInfo(['flowcrypt.compatibility.1pp1', 'flowcrypt.compatibility.2pp1']); + const encryptedData = mimeMsg.attachments.find(a => a.filename === 'encrypted.asc')!.content; + const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData, verificationPubs: [] }); + expect(decrypted.success).to.be.true; + expect(decrypted.content!.toUtfStr()).to.contain('PWD and pubkey encrypted messages with flowcrypt.com/api'); + expect((mimeMsg.to as AddressObject).text!).to.include('test@email.com'); + expect((mimeMsg.cc as AddressObject).text!).to.include('flowcrypt.compatibility@gmail.com'); + expect(mimeMsg.bcc).to.be.an.undefined; + expect(mimeMsg.headers.get('reply-to')).to.be.undefined; + await new SaveMessageInStorageStrategy().test(parseResult, id); }; } class PwdEncryptedMessageWithFlowCryptComApiTestStrategy implements ITestMsgStrategy { @@ -72,20 +66,16 @@ class PwdEncryptedMessageWithFesIdTokenTestStrategy implements ITestMsgStrategy const mimeMsg = parseResult.mimeMsg; const expectedSenderEmail = 'user@standardsubdomainfes.localhost:8001'; expect(mimeMsg.from!.text).to.equal(`First Last <${expectedSenderEmail}>`); - if (mimeMsg.text?.includes('http://fes.standardsubdomainfes.localhost:8001/message/FES-MOCK-MESSAGE-FOR-TO@EXAMPLE.COM-ID')) { - expect((mimeMsg.to as AddressObject).text).to.equal('Mr To '); - expect(mimeMsg.cc).to.be.an.undefined; - expect(mimeMsg.bcc).to.be.an.undefined; - } else if (mimeMsg.text?.includes('http://fes.standardsubdomainfes.localhost:8001/message/FES-MOCK-MESSAGE-FOR-BCC@EXAMPLE.COM-ID')) { - expect((mimeMsg.to as AddressObject).text).to.equal('Mr Bcc '); - expect(mimeMsg.cc).to.be.an.undefined; - expect(mimeMsg.bcc).to.be.an.undefined; - } else { - // no pubkey recipients in this test - throw new HttpClientErr(`Error: cannot find pwd encrypted FES link in:\n\n${mimeMsg.text}`); - } + expect((mimeMsg.to as AddressObject).text).to.equal('Mr To '); + expect(mimeMsg.cc).to.be.an.undefined; + expect((mimeMsg.bcc as AddressObject).text).to.equal('Mr Bcc '); expect(mimeMsg.text!).to.include(`${expectedSenderEmail} has sent you a password-encrypted email`); expect(mimeMsg.text!).to.include('Follow this link to open it'); + const kisWithPp = await Config.getKeyInfo(['flowcrypt.test.key.used.pgp']); + const encryptedData = mimeMsg.attachments.find(a => a.filename === 'encrypted.asc')!.content; + const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData, verificationPubs: [] }); + expect(decrypted.success).to.be.true; + expect(decrypted.content!.toUtfStr()).to.contain('PWD encrypted message with FES - ID TOKEN'); await new SaveMessageInStorageStrategy().test(parseResult, id); }; } @@ -95,51 +85,38 @@ class PwdEncryptedMessageWithFesPubkeyRecipientInBccTestStrategy implements ITes const mimeMsg = parseResult.mimeMsg; const expectedSenderEmail = 'user3@standardsubdomainfes.localhost:8001'; expect(mimeMsg.from!.text).to.equal(`First Last <${expectedSenderEmail}>`); - if (mimeMsg.text?.includes('http://fes.standardsubdomainfes.localhost:8001/message/FES-MOCK-MESSAGE-FOR-TO@EXAMPLE.COM-ID')) { - expect(mimeMsg.text!).to.include(`${expectedSenderEmail} has sent you a password-encrypted email`); - expect(mimeMsg.text!).to.include('Follow this link to open it'); - expect((mimeMsg.to as AddressObject).text).to.equal('to@example.com'); - expect(mimeMsg.cc).to.be.an.undefined; - expect(mimeMsg.bcc).to.be.an.undefined; - expect(mimeMsg.headers.get('reply-to')).to.be.an.undefined; - } else { - // this is a message to pubkey recipients - expect(mimeMsg.text!).to.not.include('has sent you a password-encrypted email'); - expect(mimeMsg.text!).to.not.include('Follow this link to open it'); - const kisWithPp = await Config.getKeyInfo(['flowcrypt.test.key.used.pgp']); - const encryptedData = Buf.fromUtfStr(mimeMsg.text!); - const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData, verificationPubs: [] }); - expect(decrypted.success).to.be.true; - expect(decrypted.content!.toUtfStr()).to.equal('PWD encrypted message with FES - pubkey recipient in bcc'); - expect((mimeMsg.bcc as AddressObject).text).to.equal('flowcrypt.compatibility@gmail.com'); - expect(mimeMsg.cc).to.be.an.undefined; - expect(mimeMsg.to).to.be.an.undefined; - expect((mimeMsg.headers.get('reply-to') as AddressObject).text).to.equal('First Last , to@example.com'); - } + // originally fes + expect(mimeMsg.text!).to.include(`${expectedSenderEmail} has sent you a password-encrypted email`); + expect(mimeMsg.text!).to.include('Follow this link to open it'); + expect((mimeMsg.to as AddressObject).text).to.equal('to@example.com'); + expect(mimeMsg.cc).to.be.an.undefined; + expect((mimeMsg.bcc as AddressObject).text).to.equal('flowcrypt.compatibility@gmail.com'); + expect(mimeMsg.headers.get('reply-to')).to.be.an.undefined; + const kisWithPp = await Config.getKeyInfo(['flowcrypt.test.key.used.pgp']); + const encryptedData = mimeMsg.attachments.find(a => a.filename === 'encrypted.asc')!.content; + const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData, verificationPubs: [] }); + expect(decrypted.success).to.be.true; + expect(decrypted.content!.toUtfStr()).to.contain('PWD encrypted message with FES - pubkey recipient'); + expect(mimeMsg.headers.get('reply-to')).to.be.undefined; await new SaveMessageInStorageStrategy().test(parseResult, id); }; } class PwdEncryptedMessageWithFesReplyBadRequestTestStrategy implements ITestMsgStrategy { - public test = async (parseResult: ParseMsgResult, id: string) => { + public test = async (parseResult: ParseMsgResult) => { const mimeMsg = parseResult.mimeMsg; const expectedSenderEmail = 'user4@standardsubdomainfes.localhost:8001'; expect(mimeMsg.from!.text).to.equal(`First Last <${expectedSenderEmail}>`); const to = parsedMailAddressObjectAsArray(mimeMsg.to) .concat(parsedMailAddressObjectAsArray(mimeMsg.cc)) .concat(parsedMailAddressObjectAsArray(mimeMsg.bcc)); - expect(to.length).to.equal(1); - const recipientEmail = to[0].text; - if (recipientEmail === 'to@example.com') { - // success - await new SaveMessageInStorageStrategy().test(parseResult, id); - return; - } else if (recipientEmail === 'invalid@example.com') { + const recipientEmails = to.map(to => to.text) + if (recipientEmails.includes('invalid@example.com')) { throw new HttpClientErr('Invalid to header', Status.BAD_REQUEST); - } else if (recipientEmail === 'timeout@example.com') { + } else if (recipientEmails.includes('timeout@example.com')) { throw new HttpClientErr('RequestTimeout', Status.BAD_REQUEST); } else { - throw new HttpClientErr(`Vague failure for ${recipientEmail}`, Status.BAD_REQUEST); + throw new HttpClientErr(`Vague failure for ${recipientEmails.join(',')}`, Status.BAD_REQUEST); } }; } @@ -149,36 +126,17 @@ class PwdEncryptedMessageWithFesReplyRenderingTestStrategy implements ITestMsgSt const mimeMsg = parseResult.mimeMsg; const expectedSenderEmail = 'user2@standardsubdomainfes.localhost:8001'; expect(mimeMsg.from!.text).to.equal(`First Last <${expectedSenderEmail}>`); - if (mimeMsg.text?.includes('http://fes.standardsubdomainfes.localhost:8001/message/FES-MOCK-MESSAGE-FOR-SENDER@DOMAIN.COM-ID')) { - expect(mimeMsg.text!).to.include(`${expectedSenderEmail} has sent you a password-encrypted email`); - expect(mimeMsg.text!).to.include('Follow this link to open it'); - expect((mimeMsg.to as AddressObject).text).to.equal('sender@domain.com'); - expect(mimeMsg.cc).to.be.an.undefined; - expect(mimeMsg.bcc).to.be.an.undefined; - expect(mimeMsg.headers.get('reply-to')).to.be.an.undefined; - } else if (mimeMsg.text?.includes('http://fes.standardsubdomainfes.localhost:8001/message/FES-MOCK-MESSAGE-FOR-TO@EXAMPLE.COM-ID')) { - expect(mimeMsg.text!).to.include(`${expectedSenderEmail} has sent you a password-encrypted email`); - expect(mimeMsg.text!).to.include('Follow this link to open it'); - expect((mimeMsg.to as AddressObject).text).to.equal('to@example.com'); - expect(mimeMsg.cc).to.be.an.undefined; - expect(mimeMsg.bcc).to.be.an.undefined; - expect(mimeMsg.headers.get('reply-to')).to.be.an.undefined; - } else { - // this is a message to pubkey recipients - expect(mimeMsg.text!).to.not.include('has sent you a password-encrypted email'); - expect(mimeMsg.text!).to.not.include('Follow this link to open it'); - const kisWithPp = await Config.getKeyInfo(['flowcrypt.test.key.used.pgp']); - const encryptedData = Buf.fromUtfStr(mimeMsg.text!); - const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData, verificationPubs: [] }); - expect(decrypted.success).to.be.true; - expect(decrypted.content!.toUtfStr()).to.include('> some dummy text'); - expect((mimeMsg.to as AddressObject).text).to.equal('flowcrypt.compatibility@gmail.com, mock.only.pubkey@flowcrypt.com'); - expect(mimeMsg.cc).to.be.an.undefined; - expect(mimeMsg.bcc).to.be.an.undefined; - expect((mimeMsg.headers.get('reply-to') as AddressObject).text).to.equal( - 'First Last , sender@domain.com, to@example.com' - ); - } + expect(mimeMsg.text).to.include(`${expectedSenderEmail} has sent you a password-encrypted email`); + expect(mimeMsg.text).to.include('Follow this link to open it'); + expect((mimeMsg.to as AddressObject).text).to.equal('sender@domain.com, flowcrypt.compatibility@gmail.com, to@example.com, mock.only.pubkey@flowcrypt.com'); + expect(mimeMsg.cc).to.be.an.undefined; + expect(mimeMsg.bcc).to.be.an.undefined; + expect(mimeMsg.headers.get('reply-to')).to.be.an.undefined; + const kisWithPp = await Config.getKeyInfo(['flowcrypt.test.key.used.pgp']); + const encryptedData = mimeMsg.attachments.find(a => a.filename === 'encrypted.asc')!.content; + const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData, verificationPubs: [] }); + expect(decrypted.success).to.be.true; + expect(decrypted.content!.toUtfStr()).to.include('> some dummy text'); await new SaveMessageInStorageStrategy().test(parseResult, id); }; } @@ -386,9 +344,9 @@ export class TestBySubjectStrategyContext { this.strategy = new PwdEncryptedMessageWithFesReplyRenderingTestStrategy(); } else if (subject.includes('PWD encrypted message with FES - pubkey recipient in bcc')) { this.strategy = new PwdEncryptedMessageWithFesPubkeyRecipientInBccTestStrategy(); - } else if (subject.includes('PWD encrypted message with FES web portal - some sends fail with BadRequest error')) { + } else if (subject.includes('PWD encrypted message with FES web portal - send fails with BadRequest error')) { this.strategy = new PwdEncryptedMessageWithFesReplyBadRequestTestStrategy(); - } else if (subject.includes('PWD encrypted message with FES web portal - a send fails with gateway update error')) { + } else if (subject.includes('PWD encrypted message with FES web portal - send fails with gateway update error')) { this.strategy = new SaveMessageInStorageStrategy(); } else if (subject.includes('Message With Image')) { this.strategy = new SaveMessageInStorageStrategy(); diff --git a/test/source/test.ts b/test/source/test.ts index 4ddf208fe3a..56e63cb39c9 100644 --- a/test/source/test.ts +++ b/test/source/test.ts @@ -146,7 +146,7 @@ ava.default.after.always('evaluate Catch.reportErr errors', async t => { 'BrowserMsg(ajax) Bad Request: 400 when GET-ing https://localhost:8001/flowcrypt-email-key-manager/v1/keys/private (no body): -> RequestTimeout', ].includes(e.message) ) - // below for test "user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - a send fails with gateway update error" + // below for test "user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - send fails with gateway update error" .filter(e => !e.message.includes('Test error')) // below for test "no.fes@example.com - skip FES on consumer, show friendly message on enterprise" .filter(e => !e.trace.includes('-1 when GET-ing https://fes.example.com')) diff --git a/test/source/tests/compose.ts b/test/source/tests/compose.ts index 7c33a3ea245..68a4842bdf0 100644 --- a/test/source/tests/compose.ts +++ b/test/source/tests/compose.ts @@ -34,7 +34,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te const acct = 'flowcrypt.compatibility@gmail.com'; const msgPwd = 'super hard password for the message'; const subject = 'PWD and pubkey encrypted messages with flowcrypt.com/api'; - const expectedNumberOfPassedMessages = (await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length + 2; + const expectedNumberOfPassedMessages = (await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length + 1; const composePage = await ComposePageRecipe.openStandalone(t, browser, 'compatibility'); await ComposePageRecipe.selectFromOption(composePage, acct); await ComposePageRecipe.fillMsg(composePage, { to: 'test@email.com', cc: 'flowcrypt.compatibility@gmail.com' }, subject); @@ -2434,7 +2434,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te await composePage.waitAndClick('@action-send', { delay: 1 }); await ComposePageRecipe.closed(composePage); const sentMsgs = (await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject); - expect(sentMsgs.length).to.equal(2); + expect(sentMsgs.length).to.equal(1); // this test is using PwdEncryptedMessageWithFesIdTokenTestStrategy to check sent result based on subject "PWD encrypted message with FES - ID TOKEN" // also see '/api/v1/message' in fes-endpoints.ts mock }) @@ -2487,7 +2487,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te 'to: sender@domain.com, flowcrypt.compatibility@gmail.com, to@example.com, mock.only.pubkey@flowcrypt.com' ); const sentMsgs = (await GoogleData.withInitializedData(acct)).getMessagesByThread('1803be2e506153d2'); - expect(sentMsgs.length).to.equal(4); // 1 original + 3 newly sent + expect(sentMsgs.length).to.equal(2); // 1 original message to reply to, 1 new sent const attachmentFrames = (composePage.target as Page).frames(); expect(attachmentFrames.length).to.equal(3); // 1 pgp block + 2 attachments expect( @@ -2533,14 +2533,14 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te await composePage.waitAndClick('@action-send', { delay: 1 }); await ComposePageRecipe.closed(composePage); const sentMsgs = (await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject); - expect(sentMsgs.length).to.equal(2); + expect(sentMsgs.length).to.equal(1); // this test is using PwdEncryptedMessageWithFesPubkeyRecipientInBccTestStrategy to check sent result based on subject "PWD encrypted message with FES - pubkey recipient in bcc" // also see '/api/v1/message' in fes-endpoints.ts mock }) ); ava.default( - 'user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - a send fails with gateway update error', + 'user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - send fails with gateway update error', testWithBrowser(undefined, async (t, browser) => { const acct = 'user4@standardsubdomainfes.localhost:8001'; // added port to trick extension into calling the mock const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); @@ -2550,7 +2550,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te { submitPubkey: false, usedPgpBefore: false }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false } ); - const subject = 'PWD encrypted message with FES web portal - a send fails with gateway update error - ' + testVariant; + const subject = 'PWD encrypted message with FES web portal - send fails with gateway update error - ' + testVariant; const expectedNumberOfPassedMessages = (await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length; const composePage = await ComposePageRecipe.openStandalone(t, browser, 'user4@standardsubdomainfes.localhost:8001'); await ComposePageRecipe.fillMsg(composePage, { to: 'gatewayfailure@example.com' }, subject); @@ -2559,7 +2559,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te await composePage.waitForContent('.ui-toast-title', 'Failed to bind Gateway ID of the message:'); await composePage.close(); expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(expectedNumberOfPassedMessages + 1); - // this test is using PwdEncryptedMessageWithFesReplyGatewayErrorTestStrategy to check sent result based on subject "PWD encrypted message with FES web portal - a send fails with gateway update error" + // this test is using SaveMessageInStorageStrategy to check sent result based on subject "PWD encrypted message with FES web portal - send fails with gateway update error" // also see '/api/v1/message' in fes-endpoints.ts mock }) ); diff --git a/test/source/tests/flaky.ts b/test/source/tests/flaky.ts index fb55220b1f3..3b058c738df 100644 --- a/test/source/tests/flaky.ts +++ b/test/source/tests/flaky.ts @@ -202,7 +202,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test ); ava.default( - 'user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - some sends fail with BadRequest error', + 'user4@standardsubdomainfes.localhost:8001 - PWD encrypted message with FES web portal - send fails with BadRequest error', testWithBrowser(undefined, async (t, browser) => { const acct = 'user4@standardsubdomainfes.localhost:8001'; // added port to trick extension into calling the mock const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); @@ -221,23 +221,10 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test /* eslint-enable @typescript-eslint/no-explicit-any */ }); await dbPage.close(); - const subject = 'PWD encrypted message with FES web portal - some sends fail with BadRequest error - ' + testVariant; - let expectedNumberOfPassedMessages = (await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length; - // 1. vague Gmail error with partial success + const subject = 'PWD encrypted message with FES web portal - send fails with BadRequest error - ' + testVariant; + const expectedNumberOfPassedMessages = (await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length; + // 1. vague Gmail error with all failures let composePage = await ComposePageRecipe.openStandalone(t, browser, 'user4@standardsubdomainfes.localhost:8001'); - await ComposePageRecipe.fillMsg(composePage, { to: 'to@example.com', cc: 'cc@example.com', bcc: 'flowcrypt.compatibility@gmail.com' }, subject); - await composePage.waitAndType('@input-password', 'gO0d-pwd'); - await composePage.waitAndClick('@action-send', { delay: 1 }); - await composePage.waitAndRespondToModal( - 'confirm', - 'cancel', - 'Messages to some recipients were sent successfully, while messages to flowcrypt.compatibility@gmail.com, Mr Cc ' + - 'encountered error(s) from Gmail. Please help us improve FlowCrypt by reporting the error to us.' - ); - await composePage.close(); - expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(++expectedNumberOfPassedMessages); - // 2. vague Gmail error with all failures - composePage = await ComposePageRecipe.openStandalone(t, browser, 'user4@standardsubdomainfes.localhost:8001'); await ComposePageRecipe.fillMsg(composePage, { cc: 'cc@example.com', bcc: 'flowcrypt.compatibility@gmail.com' }, subject); await composePage.waitAndType('@input-password', 'gO0d-pwd'); await composePage.waitAndClick('@action-send', { delay: 1 }); @@ -247,21 +234,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test 'Google returned an error when sending message. ' + 'Please help us improve FlowCrypt by reporting the error to us.' ); await composePage.close(); - expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(expectedNumberOfPassedMessages); // + 0 messages - // 3. "invalid To" Gmail error with partial success - composePage = await ComposePageRecipe.openStandalone(t, browser, 'user4@standardsubdomainfes.localhost:8001'); - await ComposePageRecipe.fillMsg(composePage, { to: 'invalid@example.com', cc: 'to@example.com' }, subject); - await composePage.waitAndType('@input-password', 'gO0d-pwd'); - await composePage.waitAndClick('@action-send', { delay: 1 }); - await composePage.waitAndRespondToModal( - 'error', - 'confirm', - 'Messages to some recipients were sent successfully, while messages to invalid@example.com ' + - 'encountered error(s) from Gmail: Invalid recipients\n\nPlease remove recipients, add them back and re-send the message.' - ); - await composePage.close(); - expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(++expectedNumberOfPassedMessages); - // 4. "invalid To" Gmail error with all failures + // 2. "invalid To" Gmail error composePage = await ComposePageRecipe.openStandalone(t, browser, 'user4@standardsubdomainfes.localhost:8001'); await ComposePageRecipe.fillMsg(composePage, { to: 'invalid@example.com', cc: 'cc@example.com' }, subject); await composePage.waitAndType('@input-password', 'gO0d-pwd'); @@ -272,21 +245,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test 'Error from google: Invalid recipients\n\nPlease remove recipients, add them back and re-send the message.' ); await composePage.close(); - expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(expectedNumberOfPassedMessages); // + 0 messages - // 5. "RequestTimeout" error with partial success - composePage = await ComposePageRecipe.openStandalone(t, browser, 'user4@standardsubdomainfes.localhost:8001'); - await ComposePageRecipe.fillMsg(composePage, { to: 'timeout@example.com', cc: 'to@example.com' }, subject); - await composePage.waitAndType('@input-password', 'gO0d-pwd'); - await composePage.waitAndClick('@action-send', { delay: 1 }); - await composePage.waitAndRespondToModal( - 'error', - 'confirm', - 'Messages to some recipients were sent successfully, while messages to timeout@example.com ' + - 'encountered network errors. Please check your internet connection and try again.' - ); - await composePage.close(); - expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(++expectedNumberOfPassedMessages); - // 6. "RequestTimeout" error with all failures + // 3. "RequestTimeout" error with all failures composePage = await ComposePageRecipe.openStandalone(t, browser, 'user4@standardsubdomainfes.localhost:8001'); await ComposePageRecipe.fillMsg(composePage, { to: 'timeout@example.com', cc: 'cc@example.com' }, subject); await composePage.waitAndType('@input-password', 'gO0d-pwd'); @@ -298,7 +257,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test '(This may also be caused by missing extension permissions).' ); await composePage.close(); - expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(expectedNumberOfPassedMessages); // + 0 messages + expect((await GoogleData.withInitializedData(acct)).searchMessagesBySubject(subject).length).to.equal(expectedNumberOfPassedMessages); // this test is using PwdEncryptedMessageWithFesReplyBadRequestTestStrategy to check sent result based on subject // "PWD encrypted message with FES web portal - some sends fail with BadRequest error" // also see '/api/v1/message' in fes-endpoints.ts mock