diff --git a/packages/tuta-wasm-loader/lib/WasmHandler.ts b/packages/tuta-wasm-loader/lib/WasmHandler.ts index 50cd2b8be0c3..71b73b87dac5 100644 --- a/packages/tuta-wasm-loader/lib/WasmHandler.ts +++ b/packages/tuta-wasm-loader/lib/WasmHandler.ts @@ -19,6 +19,7 @@ async function generateWasm(command: string, options: WasmGeneratorOptions) { ...process.env, ...options.env, }, + maxBuffer: Infinity, cwd: options.workingDir ?? process.cwd(), }) promise.child.stdout?.on("data", (data) => { @@ -37,6 +38,7 @@ async function generateWasmFallback(wasmFilePath: string, options: WasmGenerator ...process.env, ...options.env, }, + maxBuffer: Infinity, }) return result.stdout } diff --git a/src/common/api/worker/facades/lazy/MailExportFacade.ts b/src/common/api/worker/facades/lazy/MailExportFacade.ts index c2b536a69443..334984e14c43 100644 --- a/src/common/api/worker/facades/lazy/MailExportFacade.ts +++ b/src/common/api/worker/facades/lazy/MailExportFacade.ts @@ -9,6 +9,9 @@ import { BlobFacade } from "./BlobFacade.js" import { CryptoFacade } from "../../crypto/CryptoFacade.js" import { createReferencingInstance } from "../../../common/utils/BlobUtils.js" import { MailExportTokenFacade } from "./MailExportTokenFacade.js" +import { BlobAccessTokenFacade } from "../BlobAccessTokenFacade" +import { BlobServerUrl } from "../../../entities/storage/TypeRefs" +import { Group } from "../../../entities/sys/TypeRefs" assertWorkerOrNode() @@ -28,11 +31,20 @@ export class MailExportFacade { private readonly bulkMailLoader: BulkMailLoader, private readonly blobFacade: BlobFacade, private readonly cryptoFacade: CryptoFacade, + private readonly blobAccessTokenFacade: BlobAccessTokenFacade, ) {} - async loadFixedNumberOfMailsWithCache(mailListId: Id, startId: Id): Promise { + /** + * Returns a list of servers that can be used to request data from. + */ + async getExportServers(group: Group): Promise { + const blobServerAccessInfo = await this.blobAccessTokenFacade.requestWriteToken(ArchiveDataType.Attachments, group._id) + return blobServerAccessInfo.servers + } + + async loadFixedNumberOfMailsWithCache(mailListId: Id, startId: Id, baseUrl: string): Promise { return this.mailExportTokenFacade.loadWithToken((token) => - this.bulkMailLoader.loadFixedNumberOfMailsWithCache(mailListId, startId, this.options(token)), + this.bulkMailLoader.loadFixedNumberOfMailsWithCache(mailListId, startId, { baseUrl, ...this.options(token) }), ) } @@ -40,11 +52,11 @@ export class MailExportFacade { return this.mailExportTokenFacade.loadWithToken((token) => this.bulkMailLoader.loadMailDetails(mails, this.options(token))) } - async loadAttachments(mails: readonly Mail[]): Promise { - return this.mailExportTokenFacade.loadWithToken((token) => this.bulkMailLoader.loadAttachments(mails, this.options(token))) + async loadAttachments(mails: readonly Mail[], baseUrl: string): Promise { + return this.mailExportTokenFacade.loadWithToken((token) => this.bulkMailLoader.loadAttachments(mails, { baseUrl, ...this.options(token) })) } - async loadAttachmentData(mail: Mail, attachments: readonly TutanotaFile[]): Promise { + async loadAttachmentData(mail: Mail, attachments: readonly TutanotaFile[], baseUrl: string): Promise { const attachmentsWithKeys = await this.cryptoFacade.enforceSessionKeyUpdateIfNeeded(mail, attachments) // TODO: download attachments efficiently. // - download multiple blobs at once if possible @@ -52,7 +64,7 @@ export class MailExportFacade { const attachmentData = await promiseMap(attachmentsWithKeys, async (attachment) => { try { const bytes = await this.mailExportTokenFacade.loadWithToken((token) => - this.blobFacade.downloadAndDecrypt(ArchiveDataType.Attachments, createReferencingInstance(attachment), this.options(token)), + this.blobFacade.downloadAndDecrypt(ArchiveDataType.Attachments, createReferencingInstance(attachment), { baseUrl, ...this.options(token) }), ) return convertToDataFile(attachment, bytes) } catch (e) { diff --git a/src/common/api/worker/rest/EntityRestClient.ts b/src/common/api/worker/rest/EntityRestClient.ts index 2974e8d8a1c9..323ceacfb580 100644 --- a/src/common/api/worker/rest/EntityRestClient.ts +++ b/src/common/api/worker/rest/EntityRestClient.ts @@ -90,6 +90,7 @@ export interface EntityRestClientLoadOptions { ownerKeyProvider?: OwnerKeyProvider /** Defaults to {@link CacheMode.ReadAndWrite }*/ cacheMode?: CacheMode + baseUrl?: string } export interface OwnerEncSessionKeyProvider { @@ -200,6 +201,7 @@ export class EntityRestClient implements EntityRestInterface { queryParams, headers, responseType: MediaType.Json, + baseUrl: opts.baseUrl, }) const entity = JSON.parse(json) const migratedEntity = await this._crypto.applyMigrations(typeRef, entity) @@ -254,6 +256,7 @@ export class EntityRestClient implements EntityRestInterface { queryParams, headers, responseType: MediaType.Json, + baseUrl: opts.baseUrl, }) return this._handleLoadMultipleResult(typeRef, JSON.parse(json)) } @@ -281,6 +284,7 @@ export class EntityRestClient implements EntityRestInterface { queryParams, headers, responseType: MediaType.Json, + baseUrl: opts.baseUrl, }) } return this._handleLoadMultipleResult(typeRef, JSON.parse(json), ownerEncSessionKeyProvider) diff --git a/src/mail-app/native/main/MailExportController.ts b/src/mail-app/native/main/MailExportController.ts index b9cfa0c2c2bf..739ffa82d697 100644 --- a/src/mail-app/native/main/MailExportController.ts +++ b/src/mail-app/native/main/MailExportController.ts @@ -15,6 +15,7 @@ import type { TranslationText } from "../../../common/misc/LanguageViewModel" import { SuspensionError } from "../../../common/api/common/error/SuspensionError" import { Scheduler } from "../../../common/api/common/utils/Scheduler" import { ExportError, ExportErrorReason } from "../../../common/api/common/error/ExportError" +import { BlobServerUrl } from "../../../common/api/entities/storage/TypeRefs" export type MailExportState = | { type: "idle" } @@ -34,6 +35,8 @@ const TAG = "MailboxExport" export class MailExportController { private _state: Stream = stream({ type: "idle" }) private _lastExport: Date | null = null + private servers?: BlobServerUrl[] + private serverCount: number = 0 get lastExport(): Date | null { return this._lastExport @@ -142,6 +145,7 @@ export class MailExportController { private async runExport(mailboxDetail: MailboxDetail, mailBags: MailBag[], mailId: Id) { const startTime = assertNotNull(this._lastExport) + this.servers = await this.mailExportFacade.getExportServers(mailboxDetail.mailGroup) for (const mailBag of mailBags) { await this.exportMailBag(mailBag, mailId) if (this._state().type !== "exporting" || this._lastExport !== startTime) { @@ -157,17 +161,16 @@ export class MailExportController { } private async exportMailBag(mailBag: MailBag, startId: Id): Promise { - console.log(TAG, `Exporting mail bag: ${mailBag._id} ${startId}`) let currentStartId = startId while (true) { try { - const downloadedMails = await this.mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, currentStartId) + const downloadedMails = await this.mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, currentStartId, this.getServerUrl()) if (downloadedMails.length === 0) { break } const downloadedMailDetails = await this.mailExportFacade.loadMailDetails(downloadedMails) - const attachmentInfo = await this.mailExportFacade.loadAttachments(downloadedMails) + const attachmentInfo = await this.mailExportFacade.loadAttachments(downloadedMails, this.getServerUrl()) for (const { mail, mailDetails } of downloadedMailDetails) { if (this._state().type !== "exporting") { return @@ -175,7 +178,7 @@ export class MailExportController { const mailAttachmentInfo = mail.attachments .map((attachmentId) => attachmentInfo.find((attachment) => isSameId(attachment._id, attachmentId))) .filter(isNotNull) - const attachments = await this.mailExportFacade.loadAttachmentData(mail, mailAttachmentInfo) + const attachments = await this.mailExportFacade.loadAttachmentData(mail, mailAttachmentInfo, this.getServerUrl()) const { makeMailBundle } = await import("../../mail/export/Bundler.js") const mailBundle = makeMailBundle(this.sanitizer, mail, mailDetails, attachments) @@ -219,4 +222,15 @@ export class MailExportController { } } } + + private getServerUrl(): string { + if (this.servers) { + this.serverCount += 1 + if (this.serverCount >= this.servers.length) { + this.serverCount = 0 + } + return this.servers[this.serverCount].url + } + throw new Error("No servers") + } } diff --git a/src/mail-app/workerUtils/worker/WorkerLocator.ts b/src/mail-app/workerUtils/worker/WorkerLocator.ts index 5337671fdf96..a9602c4a6e13 100644 --- a/src/mail-app/workerUtils/worker/WorkerLocator.ts +++ b/src/mail-app/workerUtils/worker/WorkerLocator.ts @@ -531,7 +531,7 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData) const { MailExportFacade } = await import("../../../common/api/worker/facades/lazy/MailExportFacade.js") const { MailExportTokenFacade } = await import("../../../common/api/worker/facades/lazy/MailExportTokenFacade.js") const mailExportTokenFacade = new MailExportTokenFacade(locator.serviceExecutor) - return new MailExportFacade(mailExportTokenFacade, await locator.bulkMailLoader(), await locator.blob(), locator.crypto) + return new MailExportFacade(mailExportTokenFacade, await locator.bulkMailLoader(), await locator.blob(), locator.crypto, locator.blobAccessToken) }) } diff --git a/test/tests/api/worker/facades/MailExportFacadeTest.ts b/test/tests/api/worker/facades/MailExportFacadeTest.ts index bacbee6b549f..1eb4eb9946e9 100644 --- a/test/tests/api/worker/facades/MailExportFacadeTest.ts +++ b/test/tests/api/worker/facades/MailExportFacadeTest.ts @@ -4,11 +4,12 @@ import { MailExportTokenFacade } from "../../../../../src/common/api/worker/faca import { BulkMailLoader } from "../../../../../src/mail-app/workerUtils/index/BulkMailLoader.js" import { BlobFacade } from "../../../../../src/common/api/worker/facades/lazy/BlobFacade.js" import { CryptoFacade } from "../../../../../src/common/api/worker/crypto/CryptoFacade.js" -import { object, when } from "testdouble" +import { instance, object, when } from "testdouble" import { createTestEntity } from "../../../TestUtils.js" import { FileTypeRef, MailDetailsTypeRef, MailTypeRef } from "../../../../../src/common/api/entities/tutanota/TypeRefs.js" import { ArchiveDataType } from "../../../../../src/common/api/common/TutanotaConstants" import { createReferencingInstance } from "../../../../../src/common/api/common/utils/BlobUtils" +import { BlobAccessTokenFacade } from "../../../../../src/common/api/worker/facades/BlobAccessTokenFacade" o.spec("MailExportFacade", () => { const token = "my token" @@ -23,6 +24,7 @@ o.spec("MailExportFacade", () => { let bulkMailLoader!: BulkMailLoader let blobFacade!: BlobFacade let cryptoFacade!: CryptoFacade + let blobAccessTokenFacade!: BlobAccessTokenFacade o.beforeEach(() => { tokenFacade = { @@ -31,13 +33,17 @@ o.spec("MailExportFacade", () => { bulkMailLoader = object() blobFacade = object() cryptoFacade = object() - facade = new MailExportFacade(tokenFacade, bulkMailLoader, blobFacade, cryptoFacade) + blobAccessTokenFacade = instance(BlobAccessTokenFacade) + facade = new MailExportFacade(tokenFacade, bulkMailLoader, blobFacade, cryptoFacade, blobAccessTokenFacade) }) o.test("loadFixedNumberOfMailsWithCache", async () => { - when(bulkMailLoader.loadFixedNumberOfMailsWithCache("mailListId", "startId", { extraHeaders: tokenHeaders })).thenResolve([mail1, mail2]) + when(bulkMailLoader.loadFixedNumberOfMailsWithCache("mailListId", "startId", { baseUrl: "baseUrl", extraHeaders: tokenHeaders })).thenResolve([ + mail1, + mail2, + ]) - const result = await facade.loadFixedNumberOfMailsWithCache("mailListId", "startId") + const result = await facade.loadFixedNumberOfMailsWithCache("mailListId", "startId", "baseUrl") o(result).deepEquals([mail1, mail2]) }) @@ -56,9 +62,9 @@ o.spec("MailExportFacade", () => { o.test("loadAttachments", async () => { const expected = [createTestEntity(FileTypeRef), createTestEntity(FileTypeRef)] - when(bulkMailLoader.loadAttachments([mail1, mail2], { extraHeaders: tokenHeaders })).thenResolve(expected) + when(bulkMailLoader.loadAttachments([mail1, mail2], { baseUrl: "baseUrl", extraHeaders: tokenHeaders })).thenResolve(expected) - const result = await facade.loadAttachments([mail1, mail2]) + const result = await facade.loadAttachments([mail1, mail2], "baseUrl") o(result).deepEquals(expected) }) @@ -73,13 +79,19 @@ o.spec("MailExportFacade", () => { when(cryptoFacade.enforceSessionKeyUpdateIfNeeded(mail1, mailAttachments)).thenResolve(mailAttachments) when( - blobFacade.downloadAndDecrypt(ArchiveDataType.Attachments, createReferencingInstance(mailAttachments[0]), { extraHeaders: tokenHeaders }), + blobFacade.downloadAndDecrypt(ArchiveDataType.Attachments, createReferencingInstance(mailAttachments[0]), { + baseUrl: "baseUrl", + extraHeaders: tokenHeaders, + }), ).thenResolve(dataByteMail1) when( - blobFacade.downloadAndDecrypt(ArchiveDataType.Attachments, createReferencingInstance(mailAttachments[1]), { extraHeaders: tokenHeaders }), + blobFacade.downloadAndDecrypt(ArchiveDataType.Attachments, createReferencingInstance(mailAttachments[1]), { + baseUrl: "baseUrl", + extraHeaders: tokenHeaders, + }), ).thenResolve(dataByteMail2) - const result = await facade.loadAttachmentData(mail1, mailAttachments) + const result = await facade.loadAttachmentData(mail1, mailAttachments, "baseUrl") o(result).deepEquals([ { _type: "DataFile", name: "mail1", mimeType: "img/png", data: dataByteMail1, cid: "12345", size: 3, id: ["attachment", "id1"] }, diff --git a/test/tests/native/main/MailExportControllerTest.ts b/test/tests/native/main/MailExportControllerTest.ts index 9c92831ad0e8..446daa29fa32 100644 --- a/test/tests/native/main/MailExportControllerTest.ts +++ b/test/tests/native/main/MailExportControllerTest.ts @@ -24,11 +24,12 @@ import { GENERATED_MAX_ID, getElementId } from "../../../../src/common/api/commo import { assertNotNull } from "@tutao/tutanota-utils" import { createDataFile } from "../../../../src/common/api/common/DataFile.js" import { makeMailBundle } from "../../../../src/mail-app/mail/export/Bundler.js" -import { MailboxExportState } from "../../../../src/common/desktop/export/MailboxExportPersistence.js" import { MailExportFacade } from "../../../../src/common/api/worker/facades/lazy/MailExportFacade.js" -import { SuspensionError } from "../../../../src/common/api/common/error/SuspensionError" -import { spy } from "@tutao/tutanota-test-utils" +import { BlobServerUrlTypeRef } from "../../../../src/common/api/entities/storage/TypeRefs" import { ExportError, ExportErrorReason } from "../../../../src/common/api/common/error/ExportError" +import { MailboxExportState } from "../../../../src/common/desktop/export/MailboxExportPersistence" +import { spy } from "@tutao/tutanota-test-utils" +import { SuspensionError } from "../../../../src/common/api/common/error/SuspensionError" o.spec("MailExportController", function () { const userId = "userId" @@ -50,10 +51,16 @@ o.spec("MailExportController", function () { userController = { userId: userId } as Partial as UserController mailboxDetail = { mailbox: createTestEntity(MailBoxTypeRef, { - currentMailBag: createTestEntity(MailBagTypeRef), - archivedMailBags: [createTestEntity(MailBagTypeRef), createTestEntity(MailBagTypeRef)], + _id: "mailboxId", + currentMailBag: createTestEntity(MailBagTypeRef, { _id: "currentMailBagId", mails: "currentMailList" }), + archivedMailBags: [ + createTestEntity(MailBagTypeRef, { _id: "archivedMailBagId1", mails: "archivedMailList1" }), + createTestEntity(MailBagTypeRef, { _id: "archivedMailBagId2", mails: "archivedMailList2" }), + ], + }), + mailGroup: createTestEntity(GroupTypeRef, { + _id: "mailGroupId", }), - mailGroup: createTestEntity(GroupTypeRef), mailGroupInfo: createTestEntity(GroupInfoTypeRef), mailboxGroupRoot: createTestEntity(MailboxGroupRootTypeRef), } @@ -68,7 +75,7 @@ o.spec("MailExportController", function () { controller = new MailExportController(mailExportFacade, sanitizer, exportFacade, logins, mailboxModel, scheduler) }) - function prepareMailData(mailBag: MailBag, startId: Id) { + function prepareMailData(mailBag: MailBag, startId: Id, num: number) { const mailDetails = createTestEntity(MailDetailsTypeRef, { sentDate: new Date(42), body: createTestEntity(BodyTypeRef, { @@ -78,7 +85,7 @@ o.spec("MailExportController", function () { }) const attachmentInfo = createTestEntity(FileTypeRef, { _id: ["fileListId", "fileId"] }) const mail = createTestEntity(MailTypeRef, { - _id: ["mailListId", startId + "_1"], + _id: ["mailListId", startId + `_${num}`], attachments: [attachmentInfo._id], receivedDate: new Date(43), sender: createTestEntity(MailAddressTypeRef, { @@ -88,27 +95,33 @@ o.spec("MailExportController", function () { }) const attachmentData = new Uint8Array([1, 2, 3]) const dataFile = createDataFile("test", "application/octet-stream", attachmentData) - when(mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, startId)).thenResolve([mail]) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, startId, matchers.anything())).thenResolve([mail]) when(mailExportFacade.loadMailDetails([mail])).thenResolve([{ mail, mailDetails }]) - when(mailExportFacade.loadAttachments([mail])).thenResolve([attachmentInfo]) - when(mailExportFacade.loadAttachmentData(mail, [attachmentInfo])).thenResolve([dataFile]) + when(mailExportFacade.loadAttachments([mail], matchers.anything())).thenResolve([attachmentInfo]) + when(mailExportFacade.loadAttachmentData(mail, [attachmentInfo], matchers.anything())).thenResolve([dataFile]) const mailBundle = makeMailBundle(sanitizer, mail, mailDetails, [dataFile]) return { mail, mailBundle, mailDetails } } o.spec("startExport", function () { - o.test("it updates the initial state", function () { - controller.startExport(mailboxDetail) + o.test("it updates the initial state", async function () { + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([createTestEntity(BlobServerUrlTypeRef, { url: "baseUrl" })]) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.anything(), matchers.anything(), matchers.anything())).thenResolve([]) + await controller.startExport(mailboxDetail) verify( - exportFacade.startMailboxExport(userId, mailboxDetail.mailbox._id, assertNotNull(mailboxDetail.mailbox.currentMailBag).mails, GENERATED_MAX_ID), + exportFacade.startMailboxExport(userId, mailboxDetail.mailbox._id, assertNotNull(mailboxDetail.mailbox.currentMailBag)._id, GENERATED_MAX_ID), ) }) o.test("it runs the export", async function () { + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([createTestEntity(BlobServerUrlTypeRef, { url: "baseUrl" })]) + const mailBag = assertNotNull(mailboxDetail.mailbox.currentMailBag) - const { mail, mailBundle } = prepareMailData(mailBag, GENERATED_MAX_ID) - when(mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, getElementId(mail))).thenResolve([]) + const { mail, mailBundle } = prepareMailData(mailBag, GENERATED_MAX_ID, 1) + prepareMailData(assertNotNull(mailboxDetail.mailbox.archivedMailBags[0]), GENERATED_MAX_ID, 2) + prepareMailData(assertNotNull(mailboxDetail.mailbox.archivedMailBags[1]), GENERATED_MAX_ID, 3) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.anything(), matchers.not(GENERATED_MAX_ID), matchers.anything())).thenResolve([]) await controller.startExport(mailboxDetail) @@ -117,7 +130,7 @@ o.spec("MailExportController", function () { o.test("it sets state to locked when a LockedForUser ExportError is thrown", async function () { when( - exportFacade.startMailboxExport(userId, mailboxDetail.mailbox._id, assertNotNull(mailboxDetail.mailbox.currentMailBag).mails, GENERATED_MAX_ID), + exportFacade.startMailboxExport(userId, mailboxDetail.mailbox._id, assertNotNull(mailboxDetail.mailbox.currentMailBag)._id, GENERATED_MAX_ID), ).thenReject(new ExportError("message", ExportErrorReason.LockedForUser)) await controller.startExport(mailboxDetail) o(controller.state().type).equals("locked") @@ -135,7 +148,7 @@ o.spec("MailExportController", function () { o.test("when persisted state is running it runs the export", async function () { const initialMailId = "initialMailId" const mailBag = assertNotNull(mailboxDetail.mailbox.currentMailBag) - const { mail, mailBundle } = prepareMailData(mailBag, initialMailId) + const { mail, mailBundle } = prepareMailData(mailBag, initialMailId, 1) const persistedState: MailboxExportState = { type: "running", mailboxId: mailboxDetail.mailbox._id, @@ -146,7 +159,9 @@ o.spec("MailExportController", function () { exportDirectoryPath: "directory", } when(exportFacade.getMailboxExportState(userId)).thenResolve(persistedState) - when(mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, getElementId(mail))).thenResolve([]) + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([createTestEntity(BlobServerUrlTypeRef, { url: "baseUrl" })]) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, matchers.not(initialMailId), matchers.anything())).thenResolve([]) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.not(mailBag.mails), matchers.anything(), matchers.anything())).thenResolve([]) await controller.resumeIfNeeded() @@ -166,19 +181,25 @@ o.spec("MailExportController", function () { }) o.spec("cancelExport", function () { - o.test("canceling resets the state", function () { - controller.startExport(mailboxDetail) - controller.cancelExport() + o.test("canceling resets the state", async function () { + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([createTestEntity(BlobServerUrlTypeRef, { url: "baseUrl" })]) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.anything(), matchers.anything(), matchers.anything())).thenResolve([]) + const startPromise = controller.startExport(mailboxDetail) + const cancelPromise = controller.cancelExport() o(controller.state().type).equals("idle") + + await Promise.all([startPromise, cancelPromise]) }) }) o.spec("export loop", function () { o.test("it continues to load mail list", async function () { const mailBag = assertNotNull(mailboxDetail.mailbox.currentMailBag) - const { mail: mail1 } = prepareMailData(mailBag, GENERATED_MAX_ID) - const { mail: mail2, mailBundle: mailBundle2 } = prepareMailData(mailBag, getElementId(mail1)) - when(mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, getElementId(mail2))).thenResolve([]) + const { mail: mail1 } = prepareMailData(mailBag, GENERATED_MAX_ID, 1) + const { mail: mail2, mailBundle: mailBundle2 } = prepareMailData(mailBag, getElementId(mail1), 2) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(mailBag.mails, getElementId(mail2), matchers.anything())).thenResolve([]) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.not(mailBag.mails), matchers.anything(), matchers.anything())).thenResolve([]) + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([createTestEntity(BlobServerUrlTypeRef, { url: "baseUrl" })]) await controller.startExport(mailboxDetail) @@ -188,14 +209,15 @@ o.spec("MailExportController", function () { o.test("it loops over mail bags", async function () { const currentMailBag = assertNotNull(mailboxDetail.mailbox.currentMailBag) - const { mail: mail1, mailBundle: mailBundle1 } = prepareMailData(currentMailBag, GENERATED_MAX_ID) - when(mailExportFacade.loadFixedNumberOfMailsWithCache(currentMailBag.mails, getElementId(mail1))).thenResolve([]) + const { mail: mail1, mailBundle: mailBundle1 } = prepareMailData(currentMailBag, GENERATED_MAX_ID, 1) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(currentMailBag.mails, getElementId(mail1), "baseUrl")).thenResolve([]) const archivedMailBag1 = mailboxDetail.mailbox.archivedMailBags[0] - const { mail: mail2, mailBundle: mailBundle2 } = prepareMailData(archivedMailBag1, GENERATED_MAX_ID) - when(mailExportFacade.loadFixedNumberOfMailsWithCache(archivedMailBag1.mails, getElementId(mail2))).thenResolve([]) + const { mail: mail2, mailBundle: mailBundle2 } = prepareMailData(archivedMailBag1, GENERATED_MAX_ID, 2) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(archivedMailBag1.mails, getElementId(mail2), "baseUrl")).thenResolve([]) const archivedMailBag2 = mailboxDetail.mailbox.archivedMailBags[1] - const { mail: mail3, mailBundle: mailBundle3 } = prepareMailData(archivedMailBag2, GENERATED_MAX_ID) - when(mailExportFacade.loadFixedNumberOfMailsWithCache(archivedMailBag2.mails, getElementId(mail3))).thenResolve([]) + const { mail: mail3, mailBundle: mailBundle3 } = prepareMailData(archivedMailBag2, GENERATED_MAX_ID, 3) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(archivedMailBag2.mails, getElementId(mail3), "baseUrl")).thenResolve([]) + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([{ _id: "id", url: "baseUrl", _type: BlobServerUrlTypeRef }]) await controller.startExport(mailboxDetail) @@ -204,12 +226,31 @@ o.spec("MailExportController", function () { verify(exportFacade.saveMailboxExport(mailBundle3, userId, archivedMailBag2._id, getElementId(mail3))) verify(exportFacade.endMailboxExport(userId)) }) + + o.test("it loops over servers", async function () { + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([ + { _id: "id", url: "baseUrl1", _type: BlobServerUrlTypeRef }, + { _id: "id", url: "baseUrl2", _type: BlobServerUrlTypeRef }, + { _id: "id", url: "baseUrl3", _type: BlobServerUrlTypeRef }, + ]) + const currentMailBag = assertNotNull(mailboxDetail.mailbox.currentMailBag) + const { mail: mail1, mailBundle: mailBundle1, mailDetails: mailDetails1 } = prepareMailData(currentMailBag, GENERATED_MAX_ID, 1) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(currentMailBag.mails, getElementId(mail1), matchers.anything())).thenResolve([]) + when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.not(currentMailBag.mails), matchers.anything(), matchers.anything())).thenResolve([]) + + await controller.startExport(mailboxDetail) + + verify(mailExportFacade.loadFixedNumberOfMailsWithCache(currentMailBag.mails, GENERATED_MAX_ID, "baseUrl2")) + verify(mailExportFacade.loadAttachments([mail1], "baseUrl3")) + verify(mailExportFacade.loadAttachmentData(mail1, matchers.anything(), "baseUrl1")) + }) }) o.spec("handle errors", function () { o.test("SuspensionError", async () => { + when(mailExportFacade.getExportServers(mailboxDetail.mailGroup)).thenResolve([{ _id: "id", url: "baseUrl", _type: BlobServerUrlTypeRef }]) let wasThrown = false - when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.anything(), matchers.anything())).thenDo(() => { + when(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.anything(), matchers.anything(), matchers.anything())).thenDo(() => { if (wasThrown) { return Promise.resolve([]) } else { @@ -218,7 +259,7 @@ o.spec("MailExportController", function () { } }) await controller.startExport(mailboxDetail) - verify(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.anything(), matchers.anything()), { times: 3 + 1 }) + verify(mailExportFacade.loadFixedNumberOfMailsWithCache(matchers.anything(), matchers.anything(), matchers.anything()), { times: 3 + 1 }) o(wasThrown).equals(true) }) })