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 = ["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) 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 { serverCapabilities } } enum MockIMAPError: Error { case mailboxNotFound(String) }