import Foundation import Testing @testable import MikeMail @MainActor struct MikeMailTests { @Test func recipientSuggestionsAndInsertion() { let history = ["anna@example.com", "bert@example.com", "anna@work.de"] #expect(RecipientCompletion.matches(in: history, input: "ANN") == ["anna@example.com", "anna@work.de"]) #expect(RecipientCompletion.matches(in: history, input: "anna@example.com, ann") == ["anna@work.de"]) #expect(RecipientCompletion.matches(in: history, input: "") == []) #expect(RecipientCompletion.inserting("bert@example.com", into: "anna@example.com, be") == "anna@example.com, bert@example.com, ") #expect(RecipientCompletion.inserting("anna@example.com", into: "anna@example.com, an") == "anna@example.com, ") } @Test func imapEscapingAndMailboxNames() throws { #expect(try IMAPClient.quote("Ordner \"Büro\"") == "\"Ordner \\\"Büro\\\"\"") #expect(throws: (any Error).self) { try IMAPClient.quote("INBOX\r\nDELETE INBOX") } #expect(IMAPClient.tokens("\"/\" \"Ordner mit Leerzeichen\"") == ["/", "Ordner mit Leerzeichen"]) #expect(IMAPClient.decodeMailbox("B&APw-ro") == "Büro") #expect(IMAPClient.decodeMailbox("A &- B") == "A & B") } @Test func binaryAttachmentRoundTrip() throws { let bytes = Data((0...255).map(UInt8.init)) let attachment = MailAttachment(name: "Büroprüfung.bin", mimeType: "application/octet-stream", data: bytes) let raw = try MIME.compose(from: "sender@example.com", to: ["recipient@example.com"], subject: "Grüße aus dem Büro", body: "Zeile 1\n.\nÄÖÜ ß", attachments: [attachment]) let decoded = MIME.parse(raw, id: "1") #expect(decoded.subject == "Grüße aus dem Büro") #expect(decoded.body == "Zeile 1\n.\nÄÖÜ ß") #expect(decoded.attachments.count == 1) #expect(decoded.attachments.first?.name == "Büroprüfung.bin") #expect(decoded.attachments.first?.data == bytes) } @Test func incomingNestedMIME() { let raw = """ From: =?UTF-8?Q?J=C3=B6rg?= Subject: =?UTF-8?B?R3LDvMOfZQ==?= Date: Fri, 06 Nov 2026 10:30:00 +0100 Content-Type: multipart/mixed; boundary="outer" --outer Content-Type: multipart/alternative; boundary="inner" --inner Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Gr=C3=BC=C3=9Fe aus dem B=C3=BCro. --inner Content-Type: text/html; charset=utf-8

Alternative

--inner-- --outer Content-Type: application/pdf Content-Disposition: attachment; filename="rechnung.pdf" Content-Transfer-Encoding: base64 JVBERi0xLjc= --outer-- """ let message = MIME.parse(Data(raw.replacingOccurrences(of: "\n", with: "\r\n").utf8), id: "2") #expect(message.sender == "Jörg ") #expect(message.subject == "Grüße") #expect(message.body == "Grüße aus dem Büro.") #expect(message.attachments.first?.name == "rechnung.pdf") #expect(message.attachments.first?.data == Data("%PDF-1.7".utf8)) #expect(message.date != .distantPast) } @Test func foldedHeaderAndQuotedPrintable() { #expect(MIME.decodeHeader("=?UTF-8?Q?Gr=C3=BC=C3=9Fe?= =?UTF-8?Q?_Welt?=") == "Grüße Welt") #expect(MIME.headers("Subject: Erste\r\n zweite\r\nFrom: a@example.com")["subject"] == "Erste zweite") #expect(MIME.quotedPrintable(Data("a=\r\nb=3Dc".utf8)) == Data("ab=c".utf8)) } @Test func preventsHeaderInjection() { #expect(throws: (any Error).self) { try MIME.compose(from: "a@example.com", to: ["b@example.com\r\nBcc: x@example.com"], subject: "Hallo", body: "", attachments: []) } #expect(throws: (any Error).self) { try MIME.compose(from: "a@example.com", to: ["b@example.com"], subject: "Hallo\r\nBcc: x@example.com", body: "", attachments: []) } #expect(!MIME.validAddress("not an address")) } }