Files
MagnumOpus/Packages/MagnumOpusCore/Tests/SyncEngineTests/MockIMAPClient.swift
T
felixfoertsch 427f197bb3 add IMAP write operations, special folder role detection
Extend IMAPClientProtocol with storeFlags, moveMessage, copyMessage,
expunge, appendMessage, capabilities methods. Implement all six in
IMAPClient actor using NIOIMAPCore typed commands. Add multi-part
command support to IMAPConnection/IMAPCommandRunner for APPEND.
MockIMAPClient tracks all write calls for testing. SyncCoordinator
detects mailbox roles from LIST attributes with name-based fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 05:13:39 +01:00

94 lines
2.7 KiB
Swift

import Foundation
import IMAPClient
import Models
final class MockIMAPClient: IMAPClientProtocol, @unchecked Sendable {
var mailboxes: [IMAPMailboxInfo] = []
var mailboxStatuses: [String: IMAPMailboxStatus] = [:]
var envelopes: [FetchedEnvelope] = []
/// Per-mailbox envelopes takes precedence over flat `envelopes` when set
var mailboxEnvelopes: [String: [FetchedEnvelope]] = [:]
var flagUpdates: [UIDFlagsPair] = []
var bodies: [Int: (text: String?, html: String?)] = [:]
var connectCalled = false
var disconnectCalled = false
var selectedMailbox: String?
// v0.3 write operation tracking
var storedFlags: [(uid: Int, mailbox: String, add: [String], remove: [String])] = []
var movedMessages: [(uid: Int, from: String, to: String)] = []
var copiedMessages: [(uid: Int, from: String, to: String)] = []
var expungedMailboxes: [String] = []
var appendedMessages: [(mailbox: String, message: Data, flags: [String])] = []
var serverCapabilities: Set<String> = ["IMAP4rev1", "MOVE"]
func connect() async throws {
connectCalled = true
}
func disconnect() async throws {
disconnectCalled = true
}
func listMailboxes() async throws -> [IMAPMailboxInfo] {
mailboxes
}
func selectMailbox(_ name: String) async throws -> IMAPMailboxStatus {
selectedMailbox = name
guard let status = mailboxStatuses[name] else {
throw MockIMAPError.mailboxNotFound(name)
}
return status
}
func fetchEnvelopes(uidsGreaterThan uid: Int) async throws -> [FetchedEnvelope] {
let source: [FetchedEnvelope]
if let mailbox = selectedMailbox, let perMailbox = mailboxEnvelopes[mailbox] {
source = perMailbox
} else {
source = envelopes
}
return source.filter { $0.uid > uid }
}
func fetchFlags(uids: ClosedRange<Int>) async throws -> [UIDFlagsPair] {
flagUpdates.filter { uids.contains($0.uid) }
}
func fetchBody(uid: Int) async throws -> (text: String?, html: String?) {
bodies[uid] ?? (nil, nil)
}
// MARK: - v0.3 write operations
func storeFlags(uid: Int, mailbox: String, add: [String], remove: [String]) async throws {
storedFlags.append((uid: uid, mailbox: mailbox, add: add, remove: remove))
}
func moveMessage(uid: Int, from: String, to: String) async throws {
movedMessages.append((uid: uid, from: from, to: to))
}
func copyMessage(uid: Int, from: String, to: String) async throws {
copiedMessages.append((uid: uid, from: from, to: to))
}
func expunge(mailbox: String) async throws {
expungedMailboxes.append(mailbox)
}
func appendMessage(to mailbox: String, message: Data, flags: [String]) async throws {
appendedMessages.append((mailbox: mailbox, message: message, flags: flags))
}
func capabilities() async throws -> Set<String> {
serverCapabilities
}
}
enum MockIMAPError: Error {
case mailboxNotFound(String)
}