Closer/iphone/CloserTests/CryptoTests/MockFirestoreInvites.swift

62 lines
2.1 KiB
Swift

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
)
}
}