185 lines
7.3 KiB
Swift
185 lines
7.3 KiB
Swift
//
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import GRDB
|
|
|
|
#if TESTABLE_BUILD
|
|
|
|
public class MockSSKEnvironment {
|
|
/// Set up a mock SSK environment as well as ``DependenciesBridge``.
|
|
@MainActor
|
|
public static func activate(
|
|
appReadiness: any AppReadiness = AppReadinessImpl(),
|
|
callMessageHandler: any CallMessageHandler = NoopCallMessageHandler(),
|
|
currentCallProvider: any CurrentCallProvider = CurrentCallNoOpProvider(),
|
|
notificationPresenter: any NotificationPresenter = NoopNotificationPresenterImpl(),
|
|
testDependencies: AppSetup.TestDependencies? = nil,
|
|
) async {
|
|
let sampleDatabase = await initializeSampleDatabase()
|
|
_ = await _activate(
|
|
appReadiness: appReadiness,
|
|
callMessageHandler: callMessageHandler,
|
|
currentCallProvider: currentCallProvider,
|
|
notificationPresenter: notificationPresenter,
|
|
testDependencies: testDependencies,
|
|
sampleDatabase: sampleDatabase,
|
|
)
|
|
}
|
|
|
|
@MainActor
|
|
private static func _activate(
|
|
appReadiness: any AppReadiness = AppReadinessImpl(),
|
|
callMessageHandler: any CallMessageHandler = NoopCallMessageHandler(),
|
|
currentCallProvider: any CurrentCallProvider = CurrentCallNoOpProvider(),
|
|
keychainStorage: MockKeychainStorage = MockKeychainStorage(),
|
|
notificationPresenter: any NotificationPresenter = NoopNotificationPresenterImpl(),
|
|
testDependencies: AppSetup.TestDependencies? = nil,
|
|
sampleDatabase: SampleDatabase?,
|
|
) async -> SampleDatabase {
|
|
owsPrecondition(!(CurrentAppContext() is TestAppContext))
|
|
owsPrecondition(!SSKEnvironment.hasShared)
|
|
owsPrecondition(!DependenciesBridge.hasShared)
|
|
|
|
let testAppContext = TestAppContext()
|
|
SetCurrentAppContext(testAppContext, isRunningTests: true)
|
|
|
|
/// Note that ``SDSDatabaseStorage/grdbDatabaseFileUrl``, through a few
|
|
/// layers of abstraction, uses the "current app context" to decide
|
|
/// where to put the database,
|
|
///
|
|
/// For a ``TestAppContext`` as configured above, this will be a
|
|
/// subdirectory of our temp directory unique to the instantiation of
|
|
/// the app context.
|
|
let databaseUrl = SDSDatabaseStorage.grdbDatabaseFileUrl
|
|
|
|
let keychainStorage: MockKeychainStorage
|
|
if let sampleDatabase {
|
|
sampleDatabase.copyTo(databaseUrl)
|
|
keychainStorage = sampleDatabase.keychainStorage.clone()
|
|
} else {
|
|
keychainStorage = MockKeychainStorage()
|
|
}
|
|
|
|
let finalContinuation = await AppSetup().start(
|
|
appContext: testAppContext,
|
|
databaseStorage: try! SDSDatabaseStorage(
|
|
appReadiness: appReadiness,
|
|
databaseFileUrl: databaseUrl,
|
|
keychainStorage: keychainStorage,
|
|
),
|
|
).migrateDatabaseSchema().initGlobals(
|
|
appContext: testAppContext,
|
|
appReadiness: appReadiness,
|
|
deviceBatteryLevelManager: nil,
|
|
deviceSleepManager: nil,
|
|
paymentsEvents: PaymentsEventsNoop(),
|
|
mobileCoinHelper: MobileCoinHelperMock(),
|
|
callMessageHandler: callMessageHandler,
|
|
currentCallProvider: currentCallProvider,
|
|
notificationPresenter: notificationPresenter,
|
|
testDependencies: testDependencies ?? AppSetup.TestDependencies(
|
|
contactManager: FakeContactsManager(),
|
|
groupV2Updates: MockGroupV2Updates(),
|
|
groupsV2: MockGroupsV2(),
|
|
messageSender: { FakeMessageSender(accountChecker: $0) },
|
|
networkManager: OWSFakeNetworkManager(appReadiness: appReadiness, libsignalNet: nil),
|
|
paymentsCurrencies: MockPaymentsCurrencies(),
|
|
paymentsHelper: MockPaymentsHelper(),
|
|
pendingReceiptRecorder: NoopPendingReceiptRecorder(),
|
|
profileManager: OWSFakeProfileManager(),
|
|
reachabilityManager: MockSSKReachabilityManager(),
|
|
remoteConfigManager: StubbableRemoteConfigManager(),
|
|
signalService: OWSSignalServiceMock(),
|
|
storageServiceManager: FakeStorageServiceManager(),
|
|
syncManager: OWSMockSyncManager(),
|
|
systemStoryManager: SystemStoryManagerMock(),
|
|
versionedProfiles: MockVersionedProfiles(),
|
|
webSocketFactory: WebSocketFactoryMock(),
|
|
),
|
|
).migrateDatabaseData()
|
|
finalContinuation.runLaunchTasksIfNeededAndReloadCaches()
|
|
return SampleDatabase(fileUrl: databaseUrl, keychainStorage: keychainStorage)
|
|
}
|
|
|
|
struct SampleDatabase {
|
|
var fileUrl: URL
|
|
var keychainStorage: MockKeychainStorage
|
|
|
|
func copyTo(_ databaseUrl: URL) {
|
|
try! FileManager.default.copyItem(at: self.fileUrl, to: databaseUrl)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private static var sampleDatabase: SampleDatabase?
|
|
|
|
@MainActor
|
|
private static func initializeSampleDatabase() async -> SampleDatabase {
|
|
if let sampleDatabase {
|
|
return sampleDatabase
|
|
}
|
|
let oldContext = CurrentAppContext()
|
|
let result = await MockSSKEnvironment._activate(sampleDatabase: nil)
|
|
try! SSKEnvironment.shared.databaseStorageRef.grdbStorage.syncTruncatingCheckpoint()
|
|
self.sampleDatabase = result
|
|
await MockSSKEnvironment.deactivateAsync(oldContext: oldContext)
|
|
return result
|
|
}
|
|
|
|
@MainActor
|
|
private static func flushAndWait() {
|
|
AssertIsOnMainThread()
|
|
|
|
waitForMainQueue()
|
|
|
|
// Wait for all pending readers/writers to finish.
|
|
SSKEnvironment.shared.databaseStorageRef.grdbStorage.pool.barrierWriteWithoutTransaction { _ in }
|
|
|
|
// Wait for the MessageProcessor to finish.
|
|
SSKEnvironment.shared.messageProcessorRef.serialQueueForTests.sync {}
|
|
|
|
// Wait for the main queue *again* in case more work was scheduled.
|
|
waitForMainQueue()
|
|
}
|
|
|
|
public static func deactivateAsync(oldContext: any AppContext) async {
|
|
await withCheckedContinuation { continuation in
|
|
DispatchQueue.main.async {
|
|
SSKEnvironment.shared.databaseStorageRef.grdbStorage.pool.barrierWriteWithoutTransaction { _ in }
|
|
SSKEnvironment.shared.messageProcessorRef.serialQueueForTests.async {
|
|
DispatchQueue.main.async {
|
|
continuation.resume()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_deactivate(oldContext: oldContext)
|
|
}
|
|
|
|
@MainActor
|
|
public static func deactivate(oldContext: any AppContext) {
|
|
flushAndWait()
|
|
_deactivate(oldContext: oldContext)
|
|
}
|
|
|
|
private static func _deactivate(oldContext: any AppContext) {
|
|
SetCurrentAppContext(oldContext, isRunningTests: true)
|
|
SSKEnvironment.setShared(nil, isRunningTests: true)
|
|
DependenciesBridge.setShared(nil, isRunningTests: true)
|
|
}
|
|
|
|
private static func waitForMainQueue() {
|
|
// Spin the main run loop to flush any remaining async work.
|
|
var done = false
|
|
DispatchQueue.main.async { done = true }
|
|
while !done {
|
|
CFRunLoopRunInMode(.defaultMode, 0.0, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|