From 6f8d5e390139c32b5b27d54a26a0b5f7b5cfe857 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 11:53:24 -0700 Subject: [PATCH] Fix using initialSubscriptions with @AsyncOpen and @AutoOpen (#8572) Moving the storage of intialSubscriptions from RealmConfiguration to SyncConfiguration means that AsyncOpen needs to explicitly copy them over to the newly created SyncConfiguration. --- CHANGELOG.md | 3 +- .../SwiftServerObjects.swift | 6 +- .../ObjectServerTests/SwiftSyncTestCase.swift | 2 +- .../SwiftUIServerTests.swift | 233 +++++++++++------- RealmSwift/SwiftUI.swift | 6 + 5 files changed, 158 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d233526901..bfdc478508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ x.y.z Release notes (yyyy-MM-dd) ### Fixed * ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) -* None. +* `@AutoOpen` and `@AsyncOpen` failed to use the `initialSubscriptions` set in + the configuration passed to them ([PR #8572](https://github.com/realm/realm-swift/pull/8572), since v10.50.0). diff --git a/Realm/ObjectServerTests/SwiftServerObjects.swift b/Realm/ObjectServerTests/SwiftServerObjects.swift index b1159f328c..1325861a4d 100644 --- a/Realm/ObjectServerTests/SwiftServerObjects.swift +++ b/Realm/ObjectServerTests/SwiftServerObjects.swift @@ -180,10 +180,12 @@ public class SwiftIntPrimaryKeyObject: Object { public class SwiftHugeSyncObject: Object { @Persisted(primaryKey: true) public var _id: ObjectId @Persisted public var data: Data? + @Persisted public var partition: String - public class func create() -> SwiftHugeSyncObject { + public class func create(key: String = "") -> SwiftHugeSyncObject { let fakeDataSize = 1000000 - return SwiftHugeSyncObject(value: ["data": Data(repeating: 16, count: fakeDataSize)]) + return SwiftHugeSyncObject(value: ["data": Data(repeating: 16, count: fakeDataSize), + "partition": key]) } } diff --git a/Realm/ObjectServerTests/SwiftSyncTestCase.swift b/Realm/ObjectServerTests/SwiftSyncTestCase.swift index a0d9250db4..ed77eefb32 100644 --- a/Realm/ObjectServerTests/SwiftSyncTestCase.swift +++ b/Realm/ObjectServerTests/SwiftSyncTestCase.swift @@ -185,7 +185,7 @@ open class SwiftSyncTestCase: RLMSyncTestCase { public func populateRealm() throws { try write { realm in for _ in 0...Publisher { get } +} +extension AutoOpen: AsyncOpenStateWrapper {} +extension AsyncOpen: AsyncOpenStateWrapper {} + @available(macOS 13, *) @MainActor class SwiftUIServerTests: SwiftSyncTestCase { @@ -33,13 +41,6 @@ class SwiftUIServerTests: SwiftSyncTestCase { [SwiftHugeSyncObject.self] } - // Configuration for tests - private func configuration(user: User, partition: T) -> Realm.Configuration { - var userConfiguration = user.configuration(partitionValue: partition) - userConfiguration.objectTypes = self.objectTypes - return userConfiguration - } - override func tearDown() { cancellables.forEach { $0.cancel() } cancellables = [] @@ -48,29 +49,30 @@ class SwiftUIServerTests: SwiftSyncTestCase { var cancellables: Set = [] - // MARK: - AsyncOpen - func asyncOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration?, - timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { - let asyncOpen = AsyncOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration ?? Realm.Configuration(objectTypes: self.objectTypes), - timeout: timeout) - _ = asyncOpen.wrappedValue // Retrieving the wrappedValue to simulate a SwiftUI environment where this is called when initialising the view. - asyncOpen.projectedValue + func awaitOpen(_ wrapper: some AsyncOpenStateWrapper, + handler: @escaping (AsyncOpenState) -> Void) { + _ = wrapper.wrappedValue // Retrieving the wrappedValue to simulate a SwiftUI environment where this is called when initialising the view. + wrapper.projectedValue .sink(receiveValue: handler) .store(in: &cancellables) waitForExpectations(timeout: 10.0) - asyncOpen.cancel() + wrapper.cancel() + } + + // Configuration for tests + func configuration(user: User, partition: String) -> Realm.Configuration { + fatalError() + } + + // MARK: - AsyncOpen + func asyncOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, + timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { + fatalError() } func asyncOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { - let configuration = self.configuration(user: user, partition: partitionValue) - asyncOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout, - handler: handler) + fatalError() } func asyncOpen(handler: @escaping (AsyncOpenState) -> Void) throws { @@ -251,28 +253,14 @@ class SwiftUIServerTests: SwiftSyncTestCase { } // MARK: - AutoOpen - func autoOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration?, + func autoOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, timeout: UInt?, handler: @escaping (AsyncOpenState) -> Void) { - let autoOpen = AutoOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout) - _ = autoOpen.wrappedValue // Retrieving the wrappedValue to simulate a SwiftUI environment where this is called when initialising the view. - autoOpen.projectedValue - .sink(receiveValue: handler) - .store(in: &cancellables) - waitForExpectations(timeout: 10.0) - autoOpen.cancel() + fatalError() } func autoOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { - let configuration = self.configuration(user: user, partition: partitionValue) - autoOpen(appId: appId, - partitionValue: partitionValue, - configuration: configuration, - timeout: timeout, - handler: handler) + fatalError() } func autoOpen(handler: @escaping (AsyncOpenState) -> Void) throws { @@ -338,54 +326,6 @@ class SwiftUIServerTests: SwiftSyncTestCase { proxy.stop() } - #if false - // In case of no internet connection AutoOpen should return an opened Realm, offline-first approach - func testAutoOpenOpenForFlexibleSyncConfigWithoutInternetConnection() throws { - try autoreleasepool { - try write { realm in - for i in 1...10 { - // Using firstname to query only objects from this test - let person = SwiftPerson(firstName: "\(name)", - lastName: "lastname_\(i)", - age: i) - realm.add(person) - } - } - } - resetAppCache() - - let proxy = TimeoutProxyServer(port: 5678, targetPort: 9090) - try proxy.start() - let appConfig = AppConfiguration(baseURL: "http://localhost:5678", - transport: AsyncOpenConnectionTimeoutTransport()) - let app = App(id: flexibleSyncAppId, configuration: appConfig) - - let user = try logInUser(for: basicCredentials(app: app), app: app) - var configuration = user.flexibleSyncConfiguration() - configuration.objectTypes = [SwiftPerson.self] - - proxy.dropConnections = true - let ex = expectation(description: "download-realm-flexible-auto-open-no-connection") - let autoOpen = AutoOpen(appId: flexibleSyncAppId, configuration: configuration, timeout: 1000) - - _ = autoOpen.wrappedValue // Retrieving the wrappedValue to simulate a SwiftUI environment where this is called when initialising the view. - autoOpen.projectedValue - .sink { autoOpenState in - if case let .open(realm) = autoOpenState { - XCTAssertTrue(realm.isEmpty) // should not have downloaded anything - ex.fulfill() - } - } - .store(in: &cancellables) - - waitForExpectations(timeout: 10.0) - autoOpen.cancel() - - App.resetAppCache() - proxy.stop() - } - #endif - func testAutoOpenProgressNotification() throws { try populateRealm() @@ -565,3 +505,120 @@ class SwiftUIServerTests: SwiftSyncTestCase { } } } + +@available(macOS 13, *) +@MainActor +class PBSSwiftUIServerTests: SwiftUIServerTests { + override func configuration(user: User, partition: String) -> Realm.Configuration { + var userConfiguration = user.configuration(partitionValue: partition) + userConfiguration.objectTypes = self.objectTypes + return userConfiguration + } + + // MARK: - AsyncOpen + override func asyncOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, + timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { + let asyncOpen = AsyncOpen(appId: appId, + partitionValue: partitionValue, + configuration: configuration, + timeout: timeout) + awaitOpen(asyncOpen, handler: handler) + } + + override func asyncOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, + handler: @escaping (AsyncOpenState) -> Void) { + let configuration = self.configuration(user: user, partition: partitionValue) + asyncOpen(appId: appId, + partitionValue: partitionValue, + configuration: configuration, + timeout: timeout, + handler: handler) + } + + override func autoOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, + timeout: UInt?, handler: @escaping (AsyncOpenState) -> Void) { + let autoOpen = AutoOpen(appId: appId, + partitionValue: partitionValue, + configuration: configuration, + timeout: timeout) + awaitOpen(autoOpen, handler: handler) + } + + override func autoOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, + handler: @escaping (AsyncOpenState) -> Void) { + let configuration = self.configuration(user: user, partition: partitionValue) + autoOpen(appId: appId, + partitionValue: partitionValue, + configuration: configuration, + timeout: timeout, + handler: handler) + } +} + +@available(macOS 13, *) +@MainActor +class FLXSwiftUIServerTests: SwiftUIServerTests { + override func createApp() throws -> String { + try createFlexibleSyncApp() + } + + override func configuration(user: User) -> Realm.Configuration { + user.flexibleSyncConfiguration { subs in + subs.append(QuerySubscription { + $0.partition == self.name + }) + } + } + + override func configuration(user: User, partition: String) -> Realm.Configuration { + var userConfiguration = user.flexibleSyncConfiguration { subs in + subs.append(QuerySubscription { + $0.partition == partition + }) + } + userConfiguration.objectTypes = self.objectTypes + return userConfiguration + } + + // MARK: - AsyncOpen + override func asyncOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, + timeout: UInt? = nil, handler: @escaping (AsyncOpenState) -> Void) { + let asyncOpen = AsyncOpen(appId: appId, + configuration: configuration, + timeout: timeout) + awaitOpen(asyncOpen, handler: handler) + } + + override func asyncOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, + handler: @escaping (AsyncOpenState) -> Void) { + let configuration = self.configuration(user: user, partition: partitionValue) + asyncOpen(appId: appId, + partitionValue: partitionValue, + configuration: configuration, + timeout: timeout, + handler: handler) + } + + override func autoOpen(appId: String?, partitionValue: String, configuration: Realm.Configuration, + timeout: UInt?, handler: @escaping (AsyncOpenState) -> Void) { + let autoOpen = AutoOpen(appId: appId, + configuration: configuration, + timeout: timeout) + awaitOpen(autoOpen, handler: handler) + } + + override func autoOpen(user: User, appId: String?, partitionValue: String, timeout: UInt? = nil, + handler: @escaping (AsyncOpenState) -> Void) { + let configuration = self.configuration(user: user, partition: partitionValue) + autoOpen(appId: appId, + partitionValue: partitionValue, + configuration: configuration, + timeout: timeout, + handler: handler) + } + + // These two tests are expecting different partition values to result in + // different Realm files, which isn't applicable to FLX + override func testAutoOpenWithDifferentPartitionValues() throws {} + override func testCombineAsyncOpenAutoOpenWithDifferentPartitionValues() throws {} +} diff --git a/RealmSwift/SwiftUI.swift b/RealmSwift/SwiftUI.swift index c689d66634..9b11d8cc1e 100644 --- a/RealmSwift/SwiftUI.swift +++ b/RealmSwift/SwiftUI.swift @@ -1531,10 +1531,16 @@ private class ObservableAsyncOpenStorage: ObservableObject { } private func asyncOpenForUser(_ user: User) { + let initialSubscriptions = configuration?.syncConfiguration?.initialSubscriptions + // Set the `syncConfiguration` depending if there is partition value (pbs) or not (flx). var config: Realm.Configuration if let partitionValue = partitionValue { config = user.configuration(partitionValue: partitionValue, cancelAsyncOpenOnNonFatalErrors: true) + } else if let initialSubscriptions { + config = user.flexibleSyncConfiguration(cancelAsyncOpenOnNonFatalErrors: true, + initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions.callback), + rerunOnOpen: initialSubscriptions.rerunOnOpen) } else { config = user.flexibleSyncConfiguration(cancelAsyncOpenOnNonFatalErrors: true) }