import Foundation @testable import Closer /// Deterministic test fake for `FirestoreInvitesProtocol`. /// /// Simulates the server-side invite create/accept flow in memory: /// - `createInvite` stores the E2EE fields keyed by the invite code. /// - `acceptInvite` returns those fields plus deterministic `coupleId` and /// `inviterUserId`, mirroring `acceptInviteCallable`. /// /// No Firebase network is exercised. public final class MockFirestoreInvites: @unchecked Sendable, FirestoreInvitesProtocol { private var invites: [String: InvitePayload] = [:] private let lock = NSLock() public var lastAcceptedCode: String? public var nextCoupleId = "couple-mock-123" public var inviterUserId = "inviter-mock-uid" public init() {} public func createInvite(uid: String, code: String, recoveryPhrase: String) async throws -> InvitePayload { lock.lock() defer { lock.unlock() } // Simulate server-side code collision if the same code is used twice. guard invites[code] == nil else { struct DuplicateCode: Error {} throw DuplicateCode() } let payload = InvitePayload( code: code, wrappedCoupleKey: "wrapped-mock-\(code)", kdfSalt: "salt-mock-\(code)", kdfParams: CoupleEncryptionManager.kdfParamsTag, encryptedRecoveryPhrase: "phrase-mock-\(code)" ) invites[code] = payload return payload } public func acceptInvite(code: String, inviterUserId: String, recoveryPhrase: String) async throws -> AcceptResult { lock.lock() defer { lock.unlock() } lastAcceptedCode = code guard let payload = invites[code] else { struct NotFound: Error {} throw NotFound() } return AcceptResult( coupleId: nextCoupleId, inviterUserId: self.inviterUserId, wrappedCoupleKey: payload.wrappedCoupleKey, kdfSalt: payload.kdfSalt, kdfParams: payload.kdfParams, encryptedRecoveryPhrase: payload.encryptedRecoveryPhrase ) } }