import Foundation import Testing @testable import MikeMail @MainActor struct MailArchiveTests { @Test func diskPersistenceIncrementalReconciliationAndRestore() throws { let base = FileManager.default.temporaryDirectory.appending(path: UUID().uuidString) defer { try? FileManager.default.removeItem(at: base) } let root = base.appending(path: "Archive") let account = MailAccount(name: "Fixture", email: "fixture@example.com", username: "fixture", host: "example.com") let folder = MailFolder(accountID: account.id, path: "INBOX", name: "Posteingang") let bytes = Data((0...255).map(UInt8.init)) let raw = try MIME.compose(from: account.email, to: ["recipient@example.com"], subject: "Offline Büro", body: "Dauerhaft verfügbar", attachments: [MailAttachment(name: "Prüfung.bin", mimeType: "application/octet-stream", data: bytes)]) var archive: MailArchive? = try MailArchive(root: root) try archive?.saveConfiguration(ArchiveConfiguration(accounts: [account], folders: [account.id: [folder]], recipients: ["recipient@example.com"])) try archive?.store(raw: raw, folderID: folder.id, validity: "10", uid: "1", flags: []) try archive?.store(raw: raw, folderID: folder.id, validity: "10", uid: "1", flags: ["\\Seen"]) #expect(try archive?.messages(folderID: folder.id).count == 1) #expect(try archive?.knownUIDs(folderID: folder.id, validity: "10") == [1]) #expect(try archive?.knownUIDs(folderID: folder.id, validity: "11") == []) archive = nil let reopened = try MailArchive(root: root) #expect(try reopened.configuration()?.accounts == [account]) #expect(try reopened.messages(folderID: folder.id).first?.attachments.first?.data == bytes) #expect(try reopened.messages(folderID: folder.id).first?.isRead == true) try reopened.reconcile(folderID: folder.id, inventory: MailboxInventory(validity: "10", flags: [1: []])) #expect(try reopened.messages(folderID: folder.id).first?.isRead == false) // Partial download of a new UID generation is durable but not mixed into the visible old generation. try reopened.store(raw: raw, folderID: folder.id, validity: "11", uid: "2", flags: [], visible: false) #expect(try reopened.messages(folderID: folder.id).count == 1) #expect(try reopened.knownUIDs(folderID: folder.id, validity: "11") == [2]) let backup = base.appending(path: "Backup") try reopened.exportSnapshot(to: backup) let restored = try MailArchive.restoreSnapshot(from: backup, to: base.appending(path: "Restored")) #expect(try restored.messages(folderID: folder.id).first?.body == "Dauerhaft verfügbar") #expect(try restored.messages(folderID: folder.id).first?.attachments.first?.data == bytes) #expect(try restored.configuration()?.recipients == ["recipient@example.com"]) // A server UIDVALIDITY reset must not attach old data to the reused UID. try reopened.reconcile(folderID: folder.id, inventory: MailboxInventory(validity: "11", flags: [1: []])) #expect(try reopened.messages(folderID: folder.id).isEmpty) let retained = base.appending(path: "RetainedBackup") try reopened.exportSnapshot(to: retained) let manifest = try JSONDecoder().decode(MailArchive.Snapshot.self, from: Data(contentsOf: retained.appending(path: "manifest.json"))) #expect(manifest.messages.count == 2) #expect(manifest.messages.allSatisfy { !$0.present }) let object = retained.appending(path: "Objects/\(MailArchive.digest(raw)).eml") try Data("corrupt".utf8).write(to: object) #expect(throws: (any Error).self) { try MailArchive.restoreSnapshot(from: retained, to: base.appending(path: "Rejected")) } #expect(!FileManager.default.fileExists(atPath: base.appending(path: "Rejected").path)) } @Test func imapInventoryParsing() throws { #expect(try IMAPClient.uidValidity(from: ["* OK [UIDVALIDITY 123] UIDs valid"]) == "123") #expect(throws: (any Error).self) { try IMAPClient.uidValidity(from: ["* OK hello"]) } let flags = try IMAPClient.parseFlags(["* 1 FETCH (UID 7 FLAGS (\\Seen \\Flagged))", "* 2 FETCH (FLAGS () UID 9)"]) #expect(flags[7] == ["\\Seen", "\\Flagged"]) #expect(flags[9] == []) } }