Skip to content

Commit

Permalink
Fix using initialSubscriptions with @AsyncOpen and @AutOOpeN (#8572)
Browse files Browse the repository at this point in the history
Moving the storage of intialSubscriptions from RealmConfiguration to
SyncConfiguration means that AsyncOpen needs to explicitly copy them over to
the newly created SyncConfiguration.
  • Loading branch information
tgoyne committed May 3, 2024
1 parent c04db32 commit 6f8d5e3
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 92 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ x.y.z Release notes (yyyy-MM-dd)

### Fixed
* <How to hit and notice issue? what was the impact?> ([#????](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).

<!-- ### Breaking Changes - ONLY INCLUDE FOR NEW MAJOR version -->

Expand Down
6 changes: 4 additions & 2 deletions Realm/ObjectServerTests/SwiftServerObjects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
}

Expand Down
2 changes: 1 addition & 1 deletion Realm/ObjectServerTests/SwiftSyncTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ open class SwiftSyncTestCase: RLMSyncTestCase {
public func populateRealm() throws {
try write { realm in
for _ in 0..<SwiftSyncTestCase.bigObjectCount {
realm.add(SwiftHugeSyncObject.create())
realm.add(SwiftHugeSyncObject.create(key: name))
}
}
}
Expand Down
233 changes: 145 additions & 88 deletions Realm/ObjectServerTests/SwiftUIServerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@ import RealmSwiftSyncTestSupport
import RealmSyncTestSupport
#endif

protocol AsyncOpenStateWrapper {
func cancel()
var wrappedValue: AsyncOpenState { get }
var projectedValue: Published<AsyncOpenState>.Publisher { get }
}
extension AutoOpen: AsyncOpenStateWrapper {}
extension AsyncOpen: AsyncOpenStateWrapper {}

@available(macOS 13, *)
@MainActor
class SwiftUIServerTests: SwiftSyncTestCase {
override var objectTypes: [ObjectBase.Type] {
[SwiftHugeSyncObject.self]
}

// Configuration for tests
private func configuration<T: BSON>(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 = []
Expand All @@ -48,29 +49,30 @@ class SwiftUIServerTests: SwiftSyncTestCase {

var cancellables: Set<AnyCancellable> = []

// 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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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<SwiftHugeSyncObject> {
$0.partition == self.name
})
}
}

override func configuration(user: User, partition: String) -> Realm.Configuration {
var userConfiguration = user.flexibleSyncConfiguration { subs in
subs.append(QuerySubscription<SwiftHugeSyncObject> {
$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 {}
}
6 changes: 6 additions & 0 deletions RealmSwift/SwiftUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit 6f8d5e3

Please sign in to comment.