import { describe, expect, it } from "vitest"; import { attachmentToBlob, buildEmlSource, createMailAttachmentFromFile, decodeMimeWords, decodeTransferBody, extractMessageAttachments, extractMessageBody, extractMessageHtmlBody, htmlToPlainText, importEmlFile, isSupportedEmlFileName, isSupportedMailFileName, isSupportedMsgFileName, msgDataToMailMessage, parseContentType, parseEmlHeaders, parseEmlSource, parseMultipartBody } from "./outlookMail"; import type { MailMessage } from "../types"; describe("outlookMail helpers", () => { it("разбирает folded EML headers", () => { expect(parseEmlHeaders("Subject: Первая строка\r\n\tвторая строка\r\nFrom: user@example.com")).toEqual({ subject: "Первая строка вторая строка", from: "user@example.com" }); }); it("разделяет заголовки и тело EML", () => { const parsed = parseEmlSource("From: a@example.com\r\nTo: b@example.com\r\n\r\nТекст письма"); expect(parsed.headers.from).toBe("a@example.com"); expect(parsed.body).toBe("Текст письма"); }); it("декодирует UTF-8 MIME words и transfer encodings", () => { expect(decodeMimeWords("=?UTF-8?B?0J/RgNC40LLQtdGC?=")).toBe("Привет"); expect(decodeMimeWords("=?windows-1251?B?z/Do4uXy?=")).toBe("Привет"); expect(decodeMimeWords("=?windows-1251?Q?=CF=F0=E8=E2=E5=F2?=")).toBe("Привет"); expect(decodeMimeWords("=?UTF-8?B?0J/RgNC4?= =?UTF-8?B?0LLQtdGC?=")).toBe("Привет"); expect(decodeTransferBody("0J/RgNC40LLQtdGC", "base64")).toBe("Привет"); expect(decodeTransferBody("=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82", "quoted-printable")).toBe("Привет"); }); it("разбирает Content-Type с quoted boundary и charset", () => { expect(parseContentType('multipart/alternative; boundary="----=_Next;Part"; charset=utf-8')).toEqual({ mediaType: "multipart/alternative", parameters: { boundary: "----=_Next;Part", charset: "utf-8" } }); }); it("разбирает multipart body на MIME-части", () => { const parts = parseMultipartBody( [ "Преамбула", "--qoffice-boundary", "Content-Type: text/plain; charset=utf-8", "", "Текстовая версия", "--qoffice-boundary", "Content-Type: text/html; charset=utf-8", "", "
HTML версия
", "--qoffice-boundary--" ].join("\r\n"), "qoffice-boundary" ); expect(parts).toEqual([ { headers: { "content-type": "text/plain; charset=utf-8" }, body: "Текстовая версия" }, { headers: { "content-type": "text/html; charset=utf-8" }, body: "HTML версия
" } ]); }); it("выбирает text/plain из multipart/alternative перед HTML", () => { const body = [ "--alt", "Content-Type: text/html; charset=utf-8", "Content-Transfer-Encoding: quoted-printable", "", "=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F HTML
", "--alt", "Content-Type: text/plain; charset=utf-8", "", "Текстовая версия", "--alt--" ].join("\r\n"); expect(extractMessageBody({ "content-type": 'multipart/alternative; boundary="alt"' }, body)).toBe("Текстовая версия"); }); it("преобразует HTML body в читаемый текст, если text/plain отсутствует", () => { const body = [ "--alt", "Content-Type: text/html; charset=utf-8", "", "Строка 1
Строка & 2
Строка 1
Строка & 2
Logo
HTML
", "--alt", "Content-Type: text/plain; charset=utf-8", "Content-Transfer-Encoding: base64", "", "0J/RgNC40LLQtdGCLCDQv9C40YHRjNC80L4=", "--alt--" ].join("\r\n"); const imported = await importEmlFile(new File([eml], "message.eml", { type: "message/rfc822" })); expect(imported.subject).toBe("Мультипарт"); expect(imported.body).toBe("Привет, письмо"); expect(imported.bodyHtml).toBe("HTML
"); expect(imported.from).toBe("sender@example.com"); expect(imported.to).toBe("reader@example.com"); expect(imported.cc).toBe("copy@example.com"); expect(imported.bcc).toBe("hidden@example.com"); expect(imported.replyTo).toBe("support@example.com"); expect(imported.messageId).toBe("HTML текст
", messageDeliveryTime: "Mon, 01 Jun 2026 10:00:00 GMT", recipients: [ { smtpAddress: "reader@example.com", recipType: "to" }, { smtpAddress: "copy@example.com", recipType: "cc" }, { smtpAddress: "hidden@example.com", recipType: "bcc" } ], attachments: [{ fileName: "report.txt", attachMimeTag: "text/plain", contentLength: 5 }] } ); expect(message).toMatchObject({ folder: "inbox", from: "sender@example.com", to: "reader@example.com", cc: "copy@example.com", bcc: "hidden@example.com", replyTo: "support@example.com", messageId: "HTML текст
", body: "HTML текст", time: "Mon, 01 Jun 2026 10:00:00 GMT", read: false, flagged: false }); expect(message.bodyHtml).toBe("HTML текст
"); expect(message.id).toMatch(/^msg-/); expect(message.attachments).toEqual([ { id: "attachment-report-txt-1", fileName: "report.txt", contentType: "text/plain", size: 5, contentBase64: "SGVsbG8=", disposition: "attachment" } ]); await expect(attachmentToBlob(message.attachments?.[0]!).text()).resolves.toBe("Hello"); }); it("собирает EML source с UTF-8 subject encoding", () => { const message: MailMessage = { id: "1", folder: "sent", from: "sender@example.com", to: "reader@example.com", cc: "copy@example.com", bcc: "hidden@example.com", replyTo: "support@example.com", messageId: "HTML text
", time: "Mon, 01 Jan 2024 10:00:00 +0000", read: true, flagged: false }; const eml = buildEmlSource(message); const parsed = parseEmlSource(eml); expect(eml).toContain("Content-Type: multipart/alternative"); expect(extractMessageBody(parsed.headers, parsed.body)).toBe("Plain text"); expect(extractMessageHtmlBody(parsed.headers, parsed.body)).toBe("HTML text
"); }); it("собирает multipart/mixed EML с вложениями и RFC 2231 filename", () => { const message: MailMessage = { id: "1", folder: "sent", from: "sender@example.com", to: "reader@example.com", cc: "copy@example.com", bcc: "hidden@example.com", replyTo: "support@example.com", messageId: "Текст
", time: "Mon, 01 Jan 2024 10:00:00 +0000", read: true, flagged: false, attachments: [ { id: "a1", fileName: "отчет.txt", contentType: "text/plain", size: 5, contentBase64: "SGVsbG8=", disposition: "attachment" } ] }; const eml = buildEmlSource(message); const parsed = parseEmlSource(eml); const attachments = extractMessageAttachments(parsed.headers, parsed.body); expect(eml).toContain("Content-Type: multipart/mixed"); expect(eml).toContain("Content-Type: multipart/alternative"); expect(eml).toContain("Cc: copy@example.com"); expect(eml).toContain("Bcc: hidden@example.com"); expect(eml).toContain("Reply-To: support@example.com"); expect(eml).toContain("Message-ID:Текст
"); expect(attachments[0]).toMatchObject({ fileName: "отчет.txt", contentType: "text/plain", contentBase64: "SGVsbG8=", size: 5 }); }); it("создает вложение из File для новых писем", async () => { const attachment = await createMailAttachmentFromFile(new File(["Привет"], "note.txt", { type: "text/plain" })); expect(attachment).toMatchObject({ fileName: "note.txt", contentType: "text/plain", size: new TextEncoder().encode("Привет").byteLength, contentBase64: "0J/RgNC40LLQtdGC", disposition: "attachment" }); expect(attachment.id).toMatch(/^attachment-note-txt-/); }); it("принимает EML и MSG filenames для импорта почты", () => { expect(isSupportedEmlFileName("message.eml")).toBe(true); expect(isSupportedMsgFileName("message.msg")).toBe(true); expect(isSupportedMailFileName("message.eml")).toBe(true); expect(isSupportedMailFileName("message.msg")).toBe(true); expect(isSupportedEmlFileName("archive.pst")).toBe(false); expect(isSupportedMailFileName("archive.pst")).toBe(false); }); });