Fallback to defaultSender when editing draft whose sender is not enabled

Close #10035
This commit is contained in:
hrb-hub 2025-12-03 18:51:08 +01:00
parent aec9f0ae5d
commit 8ae0428823
6 changed files with 349 additions and 136 deletions

View file

@ -1,6 +1,5 @@
import type { GroupInfo, GroupMembership, User } from "../../entities/sys/TypeRefs.js"
import { GroupType } from "../TutanotaConstants"
import { lang, Translation } from "../../../misc/LanguageViewModel.js"
export function getEnabledMailAddressesForGroupInfo(groupInfo: GroupInfo): string[] {
let aliases = groupInfo.mailAddressAliases.filter((alias) => alias.enabled).map((alias) => alias.mailAddress)
@ -8,6 +7,13 @@ export function getEnabledMailAddressesForGroupInfo(groupInfo: GroupInfo): strin
return aliases
}
export function isAliasEnabledForGroupInfo(groupInfo: GroupInfo, aliasAddress: string): boolean {
return (
(groupInfo.mailAddress && groupInfo.mailAddress === aliasAddress) ||
(groupInfo.mailAddressAliases.find((alias) => alias.mailAddress === aliasAddress)?.enabled ?? false)
)
}
/**
* Provides the memberships of the user with the given type. In case of area groups all groups are returned.
*/

View file

@ -113,7 +113,6 @@ import {
addressDomain,
assertNotNull,
byteLength,
contains,
defer,
freshVersioned,
getUrlDomain,
@ -131,7 +130,7 @@ import {
import { BlobFacade } from "./BlobFacade.js"
import { assertWorkerOrNode, isApp, isDesktop } from "../../../common/Env.js"
import { EntityClient } from "../../../common/EntityClient.js"
import { getEnabledMailAddressesForGroupInfo, getUserGroupMemberships } from "../../../common/utils/GroupUtils.js"
import { getUserGroupMemberships, isAliasEnabledForGroupInfo } from "../../../common/utils/GroupUtils.js"
import { containsId, elementIdPart, getElementId, getLetId, isSameId, listIdPart, stringToCustomId } from "../../../common/utils/EntityUtils.js"
import { htmlToText } from "../../../common/utils/IndexUtils.js"
import { MailBodyTooLargeError } from "../../../common/error/MailBodyTooLargeError.js"
@ -1030,13 +1029,13 @@ export class MailFacade {
return promiseFilter(getUserGroupMemberships(user, GroupType.Mail), (groupMembership) => {
return this.entityClient.load(GroupTypeRef, groupMembership.group).then((mailGroup) => {
if (mailGroup.user == null) {
return this.entityClient.load(GroupInfoTypeRef, groupMembership.groupInfo).then((mailGroupInfo) => {
return contains(getEnabledMailAddressesForGroupInfo(mailGroupInfo), mailAddress)
})
return this.entityClient
.load(GroupInfoTypeRef, groupMembership.groupInfo)
.then((mailGroupInfo) => isAliasEnabledForGroupInfo(mailGroupInfo, mailAddress))
} else if (isSameId(mailGroup.user, user._id)) {
return this.entityClient.load(GroupInfoTypeRef, user.userGroup.groupInfo).then((userGroupInfo) => {
return contains(getEnabledMailAddressesForGroupInfo(userGroupInfo), mailAddress)
})
return this.entityClient
.load(GroupInfoTypeRef, user.userGroup.groupInfo)
.then((userGroupInfo) => isAliasEnabledForGroupInfo(userGroupInfo, mailAddress))
} else {
// not supported
return false

View file

@ -44,7 +44,7 @@ import {
import Stream from "mithril/stream"
import stream from "mithril/stream"
import type { File as TutanotaFile } from "../../common/api/entities/tutanota/TypeRefs.js"
import { checkAttachmentSize, getDefaultSender, getTemplateLanguages, isUserEmail, RecipientField } from "./SharedMailUtils.js"
import { checkAttachmentSize, getDefaultSender, getTemplateLanguages, isAliasEnabledWithUser, isUserEmail, RecipientField } from "./SharedMailUtils.js"
import { cloneInlineImages, InlineImages, revokeInlineImages } from "./inlineImagesUtils.js"
import { RecipientsModel, ResolvableRecipient } from "../api/main/RecipientsModel.js"
import { getAvailableLanguageCode, getSubstitutedLanguageCode, lang, Language, languages, MaybeTranslation, TranslationKey } from "../misc/LanguageViewModel.js"
@ -590,7 +590,10 @@ export class SendMailModel {
this.recipientsResolved.getAsync()
// .toLowerCase because all our aliases and accounts are lowercased on creation
this.senderAddress = senderMailAddress?.toLowerCase() || this.getDefaultSender()
this.senderAddress =
senderMailAddress != null && isAliasEnabledWithUser(this.mailboxDetails, this.user().userGroupInfo, senderMailAddress)
? senderMailAddress.toLowerCase()
: this.getDefaultSender()
this.confidential = confidential ?? !this.user().props.defaultUnconfidential
this.attachments = []

View file

@ -2,7 +2,7 @@ import { assertMainOrNode } from "../api/common/Env.js"
import { CustomerPropertiesTypeRef, GroupInfo, User } from "../api/entities/sys/TypeRefs.js"
import { Contact, createContact, createContactMailAddress, Mail } from "../api/entities/tutanota/TypeRefs.js"
import { fullNameToFirstAndLastName, mailAddressToFirstAndLastName } from "../misc/parsing/MailAddressParser.js"
import { assertNotNull, contains, neverNull, uint8ArrayToBase64 } from "@tutao/tutanota-utils"
import { assertNotNull, neverNull, uint8ArrayToBase64 } from "@tutao/tutanota-utils"
import {
ALLOWED_IMAGE_FORMATS,
ContactAddressType,
@ -16,7 +16,7 @@ import {
TUTA_MAIL_ADDRESS_DOMAINS,
} from "../api/common/TutanotaConstants.js"
import { UserController } from "../api/main/UserController.js"
import { getEnabledMailAddressesForGroupInfo, getGroupInfoDisplayName } from "../api/common/utils/GroupUtils.js"
import { getEnabledMailAddressesForGroupInfo, getGroupInfoDisplayName, isAliasEnabledForGroupInfo } from "../api/common/utils/GroupUtils.js"
import { lang, Language, TranslationKey } from "../misc/LanguageViewModel.js"
import { MailboxDetail } from "./MailboxModel.js"
import { LoginController } from "../api/main/LoginController.js"
@ -99,13 +99,19 @@ export function getEnabledMailAddressesWithUser(mailboxDetail: MailboxDetail, us
}
}
export function isAliasEnabledWithUser(mailboxDetail: MailboxDetail, userGroupInfo: GroupInfo, aliasAddress: string): boolean {
if (isUserMailbox(mailboxDetail)) {
return isAliasEnabledForGroupInfo(userGroupInfo, aliasAddress)
} else {
return isAliasEnabledForGroupInfo(mailboxDetail.mailGroupInfo, aliasAddress)
}
}
/**
* @return {string} default mail address
*/
export function getDefaultSenderFromUser({ props, userGroupInfo }: UserController): string {
return props.defaultSender && contains(getEnabledMailAddressesForGroupInfo(userGroupInfo), props.defaultSender)
? props.defaultSender
: neverNull(userGroupInfo.mailAddress)
return props.defaultSender && isAliasEnabledForGroupInfo(userGroupInfo, props.defaultSender) ? props.defaultSender : neverNull(userGroupInfo.mailAddress)
}
export function isUserMailbox(mailboxDetails: MailboxDetail): boolean {
@ -123,7 +129,7 @@ export function getDefaultSender(logins: LoginController, mailboxDetails: Mailbo
export function isUserEmail(logins: LoginController, mailboxDetails: MailboxDetail, address: string): boolean {
if (isUserMailbox(mailboxDetails)) {
return (
contains(getEnabledMailAddressesWithUser(mailboxDetails, logins.getUserController().userGroupInfo), address) ||
isAliasEnabledWithUser(mailboxDetails, logins.getUserController().userGroupInfo, address) ||
logins.getUserController().userGroupInfo.mailAddress === address
)
} else {

View file

@ -5,7 +5,7 @@ import { showProgressDialog } from "../gui/dialogs/ProgressDialog"
import type { GroupSharingTexts } from "./GroupGuiUtils"
import { getDefaultGroupName, getInvitationGroupType, getSharedGroupName } from "./GroupUtils"
import { PartialRecipient, Recipients } from "../api/common/recipients/Recipient"
import { getDefaultSender, getEnabledMailAddressesWithUser, getSenderNameForUser } from "../mailFunctionality/SharedMailUtils.js"
import { getDefaultSender, getSenderNameForUser, isAliasEnabledWithUser } from "../mailFunctionality/SharedMailUtils.js"
export function sendShareNotificationEmail(sharedGroupInfo: GroupInfo, recipients: Array<PartialRecipient>, texts: GroupSharingTexts) {
locator.mailboxModel.getUserMailboxDetails().then((mailboxDetails) => {
@ -89,7 +89,7 @@ function _sendNotificationEmail(recipients: Recipients, subject: string, body: s
usePlaceholderForInlineImages: false,
}).html
locator.mailboxModel.getUserMailboxDetails().then(async (mailboxDetails) => {
const sender = getEnabledMailAddressesWithUser(mailboxDetails, locator.logins.getUserController().userGroupInfo).includes(senderMailAddress)
const sender = isAliasEnabledWithUser(mailboxDetails, locator.logins.getUserController().userGroupInfo, senderMailAddress)
? senderMailAddress
: getDefaultSender(locator.logins, mailboxDetails)

View file

@ -30,6 +30,7 @@ import {
GroupInfoTypeRef,
GroupMembershipTypeRef,
GroupTypeRef,
MailAddressAliasTypeRef,
UserTypeRef,
} from "../../../src/common/api/entities/sys/TypeRefs.js"
import { ConversationType, GroupType, MailMethod, OperationType } from "../../../src/common/api/common/TutanotaConstants.js"
@ -97,8 +98,8 @@ const noPatchesAndInstance: Pick<EntityUpdateData, "instance" | "patches"> = {
patches: null,
}
o.spec("SendMailModel", function () {
o.before(function () {
o.spec("SendMailModel", () => {
o.before(() => {
// we need lang initialized because the SendMailModel constructor requires some translation
lang.init(en)
})
@ -106,11 +107,12 @@ o.spec("SendMailModel", function () {
let mailboxModel: MailboxModel, entity: EntityClient, mailFacade: MailFacade, recipientsModel: RecipientsModel
let model: SendMailModel
let userController: UserController
let db: ConfigurationDatabase
let syncTracker: SyncTracker
let now: number
o.beforeEach(function () {
o.beforeEach(() => {
now = 0
entity = instance(EntityClient)
when(
@ -155,7 +157,7 @@ o.spec("SendMailModel", function () {
],
})
const userController = object<UserController>()
userController = object<UserController>()
replace(userController, "user", user)
replace(userController, "props", tutanotaProperties)
when(userController.loadCustomer()).thenResolve(createTestEntity(CustomerTypeRef))
@ -215,21 +217,21 @@ o.spec("SendMailModel", function () {
replace(model, "getDefaultSender", () => DEFAULT_SENDER_FOR_TESTING)
})
o.spec("initialization", function () {
o("initWithTemplate empty", async function () {
o.spec("initialization", () => {
o.test("initWithTemplate empty", async () => {
await model.initWithTemplate({}, "", "", [], false)
o(model.getConversationType()).equals(ConversationType.NEW)
o(model.getSubject()).equals("")
o(model.getBody()).equals("")
o(model.getDraft()).equals(null)
o(model.allRecipients().length).equals(0)
o(model.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o(model.isConfidential()).equals(true)
o(model.containsExternalRecipients()).equals(false)
o(model.getAttachments().length).equals(0)
o(model.hasMailChanged()).equals(false)("initialization should not flag mail changed")
o.check(model.getConversationType()).equals(ConversationType.NEW)
o.check(model.getSubject()).equals("")
o.check(model.getBody()).equals("")
o.check(model.getDraft()).equals(null)
o.check(model.allRecipients().length).equals(0)
o.check(model.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o.check(model.isConfidential()).equals(true)
o.check(model.containsExternalRecipients()).equals(false)
o.check(model.getAttachments().length).equals(0)
o.check(model.hasMailChanged()).equals(false)("initialization should not flag mail changed")
})
o("initWithTemplate data", async function () {
o.test("initWithTemplate data", async () => {
const initializedModel = await model.initWithTemplate(
{
to: [INTERNAL_RECIPIENT_1],
@ -240,18 +242,18 @@ o.spec("SendMailModel", function () {
false,
DEFAULT_SENDER_FOR_TESTING,
)
o(initializedModel.getConversationType()).equals(ConversationType.NEW)
o(initializedModel.getSubject()).equals(SUBJECT_LINE_1)
o(initializedModel.getBody()).equals(BODY_TEXT_1)
o(initializedModel.getDraft()).equals(null)
o(initializedModel.allRecipients().length).equals(1)
o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o(model.isConfidential()).equals(true)
o(model.containsExternalRecipients()).equals(false)
o(initializedModel.getAttachments().length).equals(0)
o(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
o.check(initializedModel.getConversationType()).equals(ConversationType.NEW)
o.check(initializedModel.getSubject()).equals(SUBJECT_LINE_1)
o.check(initializedModel.getBody()).equals(BODY_TEXT_1)
o.check(initializedModel.getDraft()).equals(null)
o.check(initializedModel.allRecipients().length).equals(1)
o.check(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o.check(model.isConfidential()).equals(true)
o.check(model.containsExternalRecipients()).equals(false)
o.check(initializedModel.getAttachments().length).equals(0)
o.check(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
})
o("initWithTemplate duplicated recipients", async function () {
o.test("initWithTemplate duplicated recipients", async () => {
const duplicate = {
name: INTERNAL_RECIPIENT_1.name,
address: INTERNAL_RECIPIENT_1.address,
@ -267,18 +269,18 @@ o.spec("SendMailModel", function () {
false,
DEFAULT_SENDER_FOR_TESTING,
)
o(initializedModel.getConversationType()).equals(ConversationType.NEW)
o(initializedModel.getSubject()).equals(SUBJECT_LINE_1)
o(initializedModel.getBody()).equals(BODY_TEXT_1)
o(initializedModel.getDraft()).equals(null)
o(initializedModel.allRecipients().length).equals(1)
o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o(model.isConfidential()).equals(true)
o(model.containsExternalRecipients()).equals(false)
o(initializedModel.getAttachments().length).equals(0)
o(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
o.check(initializedModel.getConversationType()).equals(ConversationType.NEW)
o.check(initializedModel.getSubject()).equals(SUBJECT_LINE_1)
o.check(initializedModel.getBody()).equals(BODY_TEXT_1)
o.check(initializedModel.getDraft()).equals(null)
o.check(initializedModel.allRecipients().length).equals(1)
o.check(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o.check(model.isConfidential()).equals(true)
o.check(model.containsExternalRecipients()).equals(false)
o.check(initializedModel.getAttachments().length).equals(0)
o.check(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
})
o("initWithDraft with blank data", async function () {
o.test("initWithDraft with blank data", async () => {
const conversationEntryId = testIdGenerator.newIdTuple()
const draft = createTestEntity(MailTypeRef, {
confidential: false,
@ -299,18 +301,18 @@ o.spec("SendMailModel", function () {
})
const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
o(initializedModel.getConversationType()).equals(ConversationType.REPLY)
o(initializedModel.getSubject()).equals(draft.subject)
o(initializedModel.getBody()).equals(BODY_TEXT_1)
o(initializedModel.getDraft()).equals(draft)
o(initializedModel.allRecipients().length).equals(0)
o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o(model.isConfidential()).equals(true)
o(model.containsExternalRecipients()).equals(false)
o(initializedModel.getAttachments().length).equals(0)
o(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
o.check(initializedModel.getConversationType()).equals(ConversationType.REPLY)
o.check(initializedModel.getSubject()).equals(draft.subject)
o.check(initializedModel.getBody()).equals(BODY_TEXT_1)
o.check(initializedModel.getDraft()).equals(draft)
o.check(initializedModel.allRecipients().length).equals(0)
o.check(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o.check(model.isConfidential()).equals(true)
o.check(model.containsExternalRecipients()).equals(false)
o.check(initializedModel.getAttachments().length).equals(0)
o.check(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
})
o("initWithDraft with some data", async function () {
o.test("initWithDraft with some data", async () => {
const conversationEntryId = testIdGenerator.newIdTuple()
const draft = createTestEntity(MailTypeRef, {
confidential: true,
@ -344,26 +346,223 @@ o.spec("SendMailModel", function () {
})
const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
o(initializedModel.getConversationType()).equals(ConversationType.FORWARD)
o(initializedModel.getSubject()).equals(draft.subject)
o(initializedModel.getBody()).equals(BODY_TEXT_1)
o(initializedModel.getDraft()).equals(draft)
o(initializedModel.allRecipients().length).equals(2)("Only MailAddresses with a valid address will be accepted as recipients")
o(initializedModel.toRecipients().length).equals(1)
o(initializedModel.ccRecipients().length).equals(1)
o(initializedModel.bccRecipients().length).equals(0)
o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o(model.isConfidential()).equals(true)
o(model.containsExternalRecipients()).equals(true)
o(initializedModel.getAttachments().length).equals(0)
o.check(initializedModel.getConversationType()).equals(ConversationType.FORWARD)
o.check(initializedModel.getSubject()).equals(draft.subject)
o.check(initializedModel.getBody()).equals(BODY_TEXT_1)
o.check(initializedModel.getDraft()).equals(draft)
o.check(initializedModel.allRecipients().length).equals(2)("Only MailAddresses with a valid address will be accepted as recipients")
o.check(initializedModel.toRecipients().length).equals(1)
o.check(initializedModel.ccRecipients().length).equals(1)
o.check(initializedModel.bccRecipients().length).equals(0)
o.check(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
o.check(model.isConfidential()).equals(true)
o.check(model.containsExternalRecipients()).equals(true)
o.check(initializedModel.getAttachments().length).equals(0)
})
o.test("initWithDraft with shared mailbox mailAddress as sender", async () => {
const conversationEntryId = testIdGenerator.newIdTuple()
const draft = createTestEntity(MailTypeRef, {
sender: createTestEntity(MailAddressTypeRef, {
address: "shared-mailbox@addre.ss",
}),
subject: SUBJECT_LINE_1,
conversationEntry: conversationEntryId,
})
const conversationEntry = createTestEntity(ConversationEntryTypeRef, {
_id: conversationEntryId,
mail: draft._id,
conversationType: ConversationType.NEW,
})
const recipients = createTestEntity(RecipientsTypeRef, {
toRecipients: [createTestEntity(MailAddressTypeRef)],
})
const draftDetails = createTestEntity(MailDetailsTypeRef, {
recipients,
body: createTestEntity(BodyTypeRef, { text: BODY_TEXT_1 }),
})
const mailboxDetails: MailboxDetail = {
mailbox: createTestEntity(MailBoxTypeRef),
mailGroupInfo: createTestEntity(GroupInfoTypeRef, {
mailAddress: "shared-mailbox@addre.ss",
}),
mailGroup: createTestEntity(GroupTypeRef),
mailboxGroupRoot: createTestEntity(MailboxGroupRootTypeRef),
}
replace(model, "mailboxDetails", mailboxDetails)
const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
o.check(initializedModel.getSender()).equals("shared-mailbox@addre.ss")
})
o.test("initWithDraft with user's primary alias as sender", async () => {
const conversationEntryId = testIdGenerator.newIdTuple()
const draft = createTestEntity(MailTypeRef, {
sender: createTestEntity(MailAddressTypeRef, {
address: "primary-alias@tutanota.de",
}),
subject: SUBJECT_LINE_1,
conversationEntry: conversationEntryId,
})
const conversationEntry = createTestEntity(ConversationEntryTypeRef, {
_id: conversationEntryId,
mail: draft._id,
conversationType: ConversationType.NEW,
})
const recipients = createTestEntity(RecipientsTypeRef, {
toRecipients: [createTestEntity(MailAddressTypeRef)],
})
const draftDetails = createTestEntity(MailDetailsTypeRef, {
recipients,
body: createTestEntity(BodyTypeRef, { text: BODY_TEXT_1 }),
})
const mailboxDetails: MailboxDetail = {
mailbox: createTestEntity(MailBoxTypeRef),
mailGroupInfo: createTestEntity(GroupInfoTypeRef, {
mailAddress: "primary-alias@tutanota.de",
}),
mailGroup: createTestEntity(GroupTypeRef, {
user: "user-id",
}),
mailboxGroupRoot: createTestEntity(MailboxGroupRootTypeRef),
}
replace(model, "mailboxDetails", mailboxDetails)
replace(userController, "userGroupInfo", mailboxDetails.mailGroupInfo)
const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
o.check(initializedModel.getSender()).equals("primary-alias@tutanota.de")
})
o.test("initWithDraft with enabled alias as sender", async () => {
const conversationEntryId = testIdGenerator.newIdTuple()
const draft = createTestEntity(MailTypeRef, {
sender: createTestEntity(MailAddressTypeRef, {
address: "enabled-alias@tutanota.de",
}),
subject: SUBJECT_LINE_1,
conversationEntry: conversationEntryId,
})
const conversationEntry = createTestEntity(ConversationEntryTypeRef, {
_id: conversationEntryId,
mail: draft._id,
conversationType: ConversationType.NEW,
})
const recipients = createTestEntity(RecipientsTypeRef, {
toRecipients: [createTestEntity(MailAddressTypeRef)],
})
const draftDetails = createTestEntity(MailDetailsTypeRef, {
recipients,
body: createTestEntity(BodyTypeRef, { text: BODY_TEXT_1 }),
})
const mailboxDetails: MailboxDetail = {
mailbox: createTestEntity(MailBoxTypeRef),
mailGroupInfo: createTestEntity(GroupInfoTypeRef, {
mailAddress: "primary-alias@tutanota.de",
mailAddressAliases: [
createTestEntity(MailAddressAliasTypeRef, {
mailAddress: "enabled-alias@tutanota.de",
enabled: true,
}),
],
}),
mailGroup: createTestEntity(GroupTypeRef, {
user: "user-id",
}),
mailboxGroupRoot: createTestEntity(MailboxGroupRootTypeRef),
}
replace(model, "mailboxDetails", mailboxDetails)
replace(userController, "userGroupInfo", mailboxDetails.mailGroupInfo)
const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
o.check(initializedModel.getSender()).equals("enabled-alias@tutanota.de")
})
o.test("initWithDraft with deactivated alias as sender", async () => {
const conversationEntryId = testIdGenerator.newIdTuple()
const draft = createTestEntity(MailTypeRef, {
sender: createTestEntity(MailAddressTypeRef, {
address: "deactivated-alias@tutanota.de",
}),
subject: SUBJECT_LINE_1,
conversationEntry: conversationEntryId,
})
const conversationEntry = createTestEntity(ConversationEntryTypeRef, {
_id: conversationEntryId,
mail: draft._id,
conversationType: ConversationType.NEW,
})
const recipients = createTestEntity(RecipientsTypeRef, {
toRecipients: [createTestEntity(MailAddressTypeRef)],
})
const draftDetails = createTestEntity(MailDetailsTypeRef, {
recipients,
body: createTestEntity(BodyTypeRef, { text: BODY_TEXT_1 }),
})
const mailboxDetails: MailboxDetail = {
mailbox: createTestEntity(MailBoxTypeRef),
mailGroupInfo: createTestEntity(GroupInfoTypeRef, {
mailAddressAliases: [
createTestEntity(MailAddressAliasTypeRef, {
mailAddress: "deactivated-alias@tutanota.de",
enabled: false,
}),
],
}),
mailGroup: createTestEntity(GroupTypeRef, {
user: "user-id",
}),
mailboxGroupRoot: createTestEntity(MailboxGroupRootTypeRef),
}
replace(model, "mailboxDetails", mailboxDetails)
replace(userController, "userGroupInfo", mailboxDetails.mailGroupInfo)
const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
o.check(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
})
o.test("initWithDraft with deleted custom domain alias as sender", async () => {
const conversationEntryId = testIdGenerator.newIdTuple()
const draft = createTestEntity(MailTypeRef, {
sender: createTestEntity(MailAddressTypeRef, {
address: "deleted-alias@custom.domain",
}),
subject: SUBJECT_LINE_1,
conversationEntry: conversationEntryId,
})
const conversationEntry = createTestEntity(ConversationEntryTypeRef, {
_id: conversationEntryId,
mail: draft._id,
conversationType: ConversationType.NEW,
})
const recipients = createTestEntity(RecipientsTypeRef, {
toRecipients: [createTestEntity(MailAddressTypeRef)],
})
const draftDetails = createTestEntity(MailDetailsTypeRef, {
recipients,
body: createTestEntity(BodyTypeRef, { text: BODY_TEXT_1 }),
})
const mailboxDetails: MailboxDetail = {
mailbox: createTestEntity(MailBoxTypeRef),
mailGroupInfo: createTestEntity(GroupInfoTypeRef),
mailGroup: createTestEntity(GroupTypeRef, {
user: "user-id",
}),
mailboxGroupRoot: createTestEntity(MailboxGroupRootTypeRef),
}
replace(model, "mailboxDetails", mailboxDetails)
replace(userController, "userGroupInfo", mailboxDetails.mailGroupInfo)
const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
o.check(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
})
})
o.spec("Adding and removing recipients", function () {
o.beforeEach(async function () {
o.spec("Adding and removing recipients", () => {
o.beforeEach(async () => {
await model.initWithTemplate({}, "", "", [], false, "")
})
o("adding duplicate to-recipient", async function () {
o.test("adding duplicate to-recipient", async () => {
const recipient = {
name: "sanchez",
address: "123@test.com",
@ -377,11 +576,11 @@ o.spec("SendMailModel", function () {
verify(recipientsModel.initialize(recipient), { times: 1 })
o(model.toRecipients().length).equals(1)
o(model.ccRecipients().length).equals(0)
o(model.bccRecipients().length).equals(0)
o.check(model.toRecipients().length).equals(1)
o.check(model.ccRecipients().length).equals(0)
o.check(model.bccRecipients().length).equals(0)
})
o("add different to-recipients", async function () {
o.test("add different to-recipients", async () => {
const pablo = {
name: "pablo",
address: "pablo94@test.co.uk",
@ -400,11 +599,11 @@ o.spec("SendMailModel", function () {
verify(recipientsModel.initialize(pablo))
verify(recipientsModel.initialize(cortez))
o(model.toRecipients().length).equals(2)
o(model.ccRecipients().length).equals(0)
o(model.bccRecipients().length).equals(0)
o.check(model.toRecipients().length).equals(2)
o.check(model.ccRecipients().length).equals(0)
o.check(model.bccRecipients().length).equals(0)
})
o("add duplicate recipients to different fields", async function () {
o.test("add duplicate recipients to different fields", async () => {
const recipient = {
name: "sanchez",
address: "123@test.com",
@ -416,23 +615,23 @@ o.spec("SendMailModel", function () {
verify(recipientsModel.initialize(recipient), { times: 2 })
o(model.toRecipients().length).equals(1)
o(model.ccRecipients().length).equals(1)
o(model.bccRecipients().length).equals(0)
o.check(model.toRecipients().length).equals(1)
o.check(model.ccRecipients().length).equals(1)
o.check(model.bccRecipients().length).equals(0)
})
})
o.spec("Sending", function () {
o("completely blank email", async function () {
o.spec("Sending", () => {
o.test("completely blank email", async () => {
const method = MailMethod.NONE
const getConfirmation = func<() => Promise<boolean>>()
const e = await assertThrows(UserError, () => model.send(method, getConfirmation))
o(e?.message).equals(lang.get("noRecipients_msg"))
o.check(e?.message).equals(lang.get("noRecipients_msg"))
verify(getConfirmation(), { times: 0 })
verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
verify(mailFacade.createDraft(anything()), { times: 0 })
verify(mailFacade.updateDraft(anything()), { times: 0 })
})
o("blank subject no confirm", async function () {
o.test("blank subject no confirm", async () => {
model.addRecipient(RecipientField.TO, {
name: "test",
address: "test@address.com",
@ -442,13 +641,13 @@ o.spec("SendMailModel", function () {
const method = MailMethod.NONE
const getConfirmation = func<() => Promise<boolean>>()
const r = await model.send(method, getConfirmation)
o(r).equals(false)
o.check(r).equals(false)
verify(getConfirmation(), { times: 0 })
verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
verify(mailFacade.createDraft(anything()), { times: 0 })
verify(mailFacade.updateDraft(anything()), { times: 0 })
})
o("confidential missing password", async function () {
o.test("confidential missing password", async () => {
await model.addRecipient(RecipientField.TO, {
name: "test",
address: "test@address.com",
@ -461,13 +660,13 @@ o.spec("SendMailModel", function () {
when(getConfirmation(anything())).thenResolve(true)
const e = await assertThrows(UserError, () => model.send(method, getConfirmation))
o(e?.message).equals(lang.get("noPreSharedPassword_msg"))
o.check(e?.message).equals(lang.get("noPreSharedPassword_msg"))
verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
verify(mailFacade.createDraft(anything()), { times: 0 })
verify(mailFacade.updateDraft(anything()), { times: 0 })
})
o("confidential weak password no confirm", async function () {
o.test("confidential weak password no confirm", async () => {
const recipient = {
name: "test",
address: "test@address.com",
@ -475,18 +674,18 @@ o.spec("SendMailModel", function () {
}
await model.initWithTemplate({ to: [recipient] }, "subject", "", [], true, "me@tuta.com", false)
model.setPassword("test@address.com", "abc")
o(model.getPassword(recipient.address)).equals("abc")
o.check(model.getPassword(recipient.address)).equals("abc")
const method = MailMethod.NONE
const getConfirmation = func<(TranslationKey) => Promise<boolean>>()
when(getConfirmation(anything())).thenResolve(false)
const r = await model.send(method, getConfirmation)
o(r).equals(false)
o.check(r).equals(false)
verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
verify(mailFacade.createDraft(anything()), { times: 0 })
verify(mailFacade.updateDraft(anything()), { times: 0 })
})
o("confidential weak password confirm", async function () {
o.test("confidential weak password confirm", async () => {
const recipient = {
name: "test",
address: "test@address.com",
@ -495,31 +694,31 @@ o.spec("SendMailModel", function () {
await model.initWithTemplate({ to: [recipient] }, "did you get that thing i sent ya?", "", [], true, "me@tutanota.de", false)
const password = WEAK_PASSWORD
model.setPassword("test@address.com", password)
o(model.getPassword(recipient.address)).equals(password)
o.check(model.getPassword(recipient.address)).equals(password)
const method = MailMethod.NONE
const getConfirmation = func<(TranslationKey) => Promise<boolean>>()
when(getConfirmation(anything())).thenResolve(true)
const r = await model.send(method, getConfirmation)
o(r).equals(true)
o.check(r).equals(true)
verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 1 })
verify(mailFacade.createDraft(anything()), { times: 1 })
verify(mailFacade.updateDraft(anything()), { times: 0 })
const contact = model.getRecipientList(RecipientField.TO)[0].contact!
o(contact.presharedPassword).equals(password)
o.check(contact.presharedPassword).equals(password)
})
o("correct password will be returned from getPassword after calling setPassword", function () {
o.test("correct password will be returned from getPassword after calling setPassword", () => {
model.setPassword("address1", "password1")
model.setPassword("address2", "password2")
o(model.getPassword("address2")).equals("password2")
o(model.getPassword("address1")).equals("password1")
o.check(model.getPassword("address2")).equals("password2")
o.check(model.getPassword("address1")).equals("password1")
})
o("confidential strong password", async function () {
o.test("confidential strong password", async () => {
const address = "test@address.com"
const recipient = {
name: "test",
@ -534,7 +733,7 @@ o.spec("SendMailModel", function () {
const getConfirmation = func<(TranslationKey) => Promise<boolean>>()
const r = await model.send(method, getConfirmation)
o(r).equals(true)
o.check(r).equals(true)
verify(getConfirmation(anything), { times: 0 })
@ -543,10 +742,10 @@ o.spec("SendMailModel", function () {
verify(mailFacade.updateDraft(anything()), { times: 0 })
const contact = model.getRecipientList(RecipientField.TO)[0].contact!
o(contact.presharedPassword).equals(password)
o.check(contact.presharedPassword).equals(password)
})
o("when a recipient has an existing contact, and the saved password changes, then the contact will be updated", async function () {
o.test("when a recipient has an existing contact, and the saved password changes, then the contact will be updated", async () => {
const getConfirmation = func<(TranslationKey) => Promise<boolean>>()
const contact = createTestEntity(ContactTypeRef, {
@ -569,10 +768,10 @@ o.spec("SendMailModel", function () {
})
})
o.spec("Entity Event Updates", function () {
o.spec("Entity Event Updates", () => {
let existingContact
let recipients
o.before(function () {
o.before(() => {
existingContact = createTestEntity(ContactTypeRef, {
_id: testIdGenerator.newIdTuple(),
firstName: "james",
@ -593,7 +792,7 @@ o.spec("SendMailModel", function () {
]
})
o("nonmatching event", async function () {
o.test("nonmatching event", async () => {
await model.handleEntityEvent({
typeRef: UserTypeRef,
operation: OperationType.CREATE,
@ -637,7 +836,7 @@ o.spec("SendMailModel", function () {
verify(entity.load(anything(), anything(), anything()), { times: 0 })
})
o("contact updated email kept", async function () {
o.test("contact updated email kept", async () => {
const [instanceListId, instanceId] = existingContact._id
const contactForUpdate = {
firstName: "newfirstname",
@ -666,11 +865,11 @@ o.spec("SendMailModel", function () {
...noPatchesAndInstance,
prefetchStatus: PrefetchStatus.NotPrefetched,
})
o(model.allRecipients().length).equals(2)
o.check(model.allRecipients().length).equals(2)
const updatedRecipient = model.allRecipients().find((r) => r.contact && isSameId(r.contact._id, existingContact._id))
o(updatedRecipient && updatedRecipient.name).equals(getContactDisplayName(downcast(contactForUpdate)))
o.check(updatedRecipient && updatedRecipient.name).equals(getContactDisplayName(downcast(contactForUpdate)))
})
o("contact updated email removed or changed", async function () {
o.test("contact updated email removed or changed", async () => {
const [instanceListId, instanceId] = existingContact._id
const contactForUpdate = {
firstName: "james",
@ -701,11 +900,11 @@ o.spec("SendMailModel", function () {
...noPatchesAndInstance,
prefetchStatus: PrefetchStatus.NotPrefetched,
})
o(model.allRecipients().length).equals(1)
o.check(model.allRecipients().length).equals(1)
const updatedContact = model.allRecipients().find((r) => r.contact && isSameId(r.contact._id, existingContact._id))
o(updatedContact ?? null).equals(null)
o.check(updatedContact ?? null).equals(null)
})
o("contact removed", async function () {
o.test("contact removed", async () => {
const [instanceListId, instanceId] = existingContact._id
await model.initWithTemplate({ to: recipients }, "subj", "", [], true, "a@b.c", false)
await model.handleEntityEvent({
@ -716,11 +915,11 @@ o.spec("SendMailModel", function () {
...noPatchesAndInstance,
prefetchStatus: PrefetchStatus.NotPrefetched,
})
o(model.allRecipients().length).equals(1)
o.check(model.allRecipients().length).equals(1)
const updatedContact = model.allRecipients().find((r) => r.contact && isSameId(r.contact._id, existingContact._id))
o(updatedContact == null).equals(true)
o.check(updatedContact == null).equals(true)
})
o("too many to recipients dont confirm", async function () {
o.test("too many to recipients dont confirm", async () => {
const recipients = {
to: [] as { name: string; address: string }[],
}
@ -740,10 +939,10 @@ o.spec("SendMailModel", function () {
await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de", false)
const hasBeenSent = await model.send(MailMethod.NONE, getConfirmation)
o(hasBeenSent).equals(false)("nothing was sent")
o.check(hasBeenSent).equals(false)("nothing was sent")
verify(getConfirmation("manyRecipients_msg"), { times: 1 })
})
o("too many to recipients confirm", async function () {
o.test("too many to recipients confirm", async () => {
const recipients = {
to: [] as { name: string; address: string }[],
}
@ -763,10 +962,10 @@ o.spec("SendMailModel", function () {
await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de")
o(await model.send(MailMethod.NONE, getConfirmation)).equals(true)
o.check(await model.send(MailMethod.NONE, getConfirmation)).equals(true)
verify(getConfirmation("manyRecipients_msg"), { times: 1 })
})
o("too many cc recipients dont confirm", async function () {
o.test("too many cc recipients dont confirm", async () => {
const recipients = {
cc: [] as { name: string; address: string }[],
}
@ -785,10 +984,10 @@ o.spec("SendMailModel", function () {
when(getConfirmation("manyRecipients_msg")).thenResolve(false)
await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de")
o(await model.send(MailMethod.NONE, getConfirmation)).equals(false)
o.check(await model.send(MailMethod.NONE, getConfirmation)).equals(false)
verify(getConfirmation("manyRecipients_msg"), { times: 1 })
})
o("too many cc recipients confirm", async function () {
o.test("too many cc recipients confirm", async () => {
const recipients = {
cc: [] as { name: string; address: string }[],
}
@ -807,7 +1006,7 @@ o.spec("SendMailModel", function () {
when(getConfirmation("manyRecipients_msg")).thenResolve(true)
await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de")
o(await model.send(MailMethod.NONE, getConfirmation)).equals(true)
o.check(await model.send(MailMethod.NONE, getConfirmation)).equals(true)
verify(getConfirmation("manyRecipients_msg"), { times: 1 })
})
o.spec("mail draft update", () => {