Misc. Fixes

This commit is contained in:
Dr. Michael Kübler
2026-11-10 21:20:37 +01:00
committed by Dr. Michael Kübler
parent 49138503dd
commit 986ec72af0
7 changed files with 491 additions and 39 deletions
+72
View File
@@ -4,6 +4,78 @@ import Testing
@MainActor
struct MailActionTests {
@Test func emptyTrashOnlyListsUIDsAndDeletesConfirmedBatches() async throws {
let trash = MailFolder(accountID: UUID(), path: "Trash", name: "Trash")
var commands: [String] = []
var removed: [UInt64] = []
let validity = try await IMAPClient.emptyTrash(folder: trash) { command in
commands.append(command)
switch command {
case "SELECT \"Trash\"": return .init(lines: ["* OK [UIDVALIDITY 7]"])
case "UID SEARCH ALL": return .init(lines: ["* SEARCH 10 20"])
case "CAPABILITY": return .init(lines: ["* CAPABILITY IMAP4rev1 UIDPLUS"])
case "UID STORE 10,20 +FLAGS.SILENT (\\Deleted)", "UID EXPUNGE 10,20":
return .init()
default:
Issue.record("Unexpected command: \(command)")
throw MailError.message("Unexpected command")
}
} onRemoved: { validity, uids in
#expect(validity == "7")
#expect(commands.last == "UID EXPUNGE 10,20")
removed += uids
}
#expect(validity == "7")
#expect(removed == [10, 20])
#expect(commands.count == 5)
}
@Test func partialTrashFailureKeepsConfirmedDeletionsAndOtherFolders() async throws {
let root = FileManager.default.temporaryDirectory.appending(path: UUID().uuidString)
defer { try? FileManager.default.removeItem(at: root) }
let archive = try MailArchive(root: root)
let account = UUID()
let trash = MailFolder(accountID: account, path: "Trash", name: "Trash")
let inbox = MailFolder(accountID: account, path: "INBOX", name: "Inbox")
let raw = Data("Subject: Fixture\r\n\r\nBody".utf8)
// Only a subset is cached locally; absent local records must not stop server deletion.
for uid in ["1", "201"] {
try archive.store(raw: raw, folderID: trash.id, validity: "7", uid: uid, flags: [])
}
try archive.store(raw: raw, folderID: inbox.id, validity: "7", uid: "1", flags: [])
try archive.store(raw: raw, folderID: trash.id, validity: "8", uid: "1", flags: [])
await #expect(throws: (any Error).self) {
_ = try await IMAPClient.emptyTrash(folder: trash) { command in
if command.hasPrefix("SELECT") { return .init(lines: ["* OK [UIDVALIDITY 7]"]) }
if command == "UID SEARCH ALL" {
return .init(lines: ["* SEARCH " + (1...201).map(String.init).joined(separator: " ")])
}
if command == "CAPABILITY" { return .init(lines: ["* CAPABILITY UIDPLUS"]) }
if command == "UID EXPUNGE 201" { throw MailError.message("Connection lost") }
return .init()
} onRemoved: { validity, uids in
try archive.applyRemovals(folderID: trash.id, validity: validity, uids: uids)
}
}
#expect(Set(try archive.messages(folderID: trash.id).map(\.id)) ==
[trash.id + ":7:201", trash.id + ":8:1"])
#expect(try archive.messages(folderID: inbox.id).count == 1)
}
@Test func emptyTrashRequiresUIDPLUSBeforeDeleting() async throws {
let trash = MailFolder(accountID: UUID(), path: "Trash", name: "Trash")
var commands: [String] = []
await #expect(throws: (any Error).self) {
_ = try await IMAPClient.emptyTrash(folder: trash) { command in
commands.append(command)
if command.hasPrefix("SELECT") { return .init(lines: ["* OK [UIDVALIDITY 7]"]) }
if command == "UID SEARCH ALL" { return .init(lines: ["* SEARCH 1"]) }
return .init(lines: ["* CAPABILITY IMAP4rev1"])
} onRemoved: { _, _ in Issue.record("No deletion was confirmed") }
}
#expect(commands == ["SELECT \"Trash\"", "UID SEARCH ALL", "CAPABILITY"])
}
@Test func ccAndBccStaySeparateFromEnvelope() throws {
let raw = try MIME.compose(from: "sender@example.com", to: ["to@example.com"],
cc: ["copy@example.com"], bcc: ["hidden@example.com"], subject: "CC BCC", body: "Hello", attachments: [])