//////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// import XCTest import RealmSwift // Used by testOfflineClientReset // The naming here is nonstandard as the sync-1.x.realm test file comes from the .NET unit tests. // swiftlint:disable identifier_name @objc(Person) class Person: Object { @objc dynamic var FirstName: String? @objc dynamic var LastName: String? override class func shouldIncludeInDefaultSchema() -> Bool { return false } } class SwiftObjectServerTests: SwiftSyncTestCase { /// It should be possible to successfully open a Realm configured for sync. func testBasicSwiftSync() { let url = URL(string: "realm://127.0.0.1:9080/~/testBasicSync")! do { let user = try synchronouslyLogInUser(for: basicCredentials(register: true), server: authURL) let realm = try synchronouslyOpenRealm(url: url, user: user) XCTAssert(realm.isEmpty, "Freshly synced Realm was not empty...") } catch { XCTFail("Got an error: \(error)") } } /// If client B adds objects to a Realm, client A should see those new objects. func testSwiftAddObjects() { do { let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL) let realm = try synchronouslyOpenRealm(url: realmURL, user: user) if isParent { waitForDownloads(for: realm) checkCount(expected: 0, realm, SwiftSyncObject.self) executeChild() waitForDownloads(for: realm) checkCount(expected: 3, realm, SwiftSyncObject.self) } else { // Add objects try realm.write { realm.add(SwiftSyncObject(value: ["child-1"])) realm.add(SwiftSyncObject(value: ["child-2"])) realm.add(SwiftSyncObject(value: ["child-3"])) } waitForUploads(for: realm) checkCount(expected: 3, realm, SwiftSyncObject.self) } } catch { XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))") } } /// If client B removes objects from a Realm, client A should see those changes. func testSwiftDeleteObjects() { do { let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL) let realm = try synchronouslyOpenRealm(url: realmURL, user: user) if isParent { try realm.write { realm.add(SwiftSyncObject(value: ["child-1"])) realm.add(SwiftSyncObject(value: ["child-2"])) realm.add(SwiftSyncObject(value: ["child-3"])) } waitForUploads(for: realm) checkCount(expected: 3, realm, SwiftSyncObject.self) executeChild() waitForDownloads(for: realm) checkCount(expected: 0, realm, SwiftSyncObject.self) } else { try realm.write { realm.deleteAll() } waitForUploads(for: realm) checkCount(expected: 0, realm, SwiftSyncObject.self) } } catch { XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))") } } func testConnectionState() { let user = try! synchronouslyLogInUser(for: basicCredentials(register: true), server: authURL) let realm = try! synchronouslyOpenRealm(url: realmURL, user: user) let session = realm.syncSession! func wait(forState desiredState: SyncSession.ConnectionState) { let ex = expectation(description: "Wait for connection state: \(desiredState)") let token = session.observe(\SyncSession.connectionState, options: .initial) { s, _ in if s.connectionState == desiredState { ex.fulfill() } } waitForExpectations(timeout: 2.0) token.invalidate() } wait(forState: .connected) session.suspend() wait(forState: .disconnected) session.resume() wait(forState: .connecting) wait(forState: .connected) } // MARK: - Client reset func testClientReset() { do { let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL) let realm = try synchronouslyOpenRealm(url: realmURL, user: user) var theError: SyncError? let ex = expectation(description: "Waiting for error handler to be called...") SyncManager.shared.errorHandler = { (error, session) in if let error = error as? SyncError { theError = error } else { XCTFail("Error \(error) was not a sync error. Something is wrong.") } ex.fulfill() } user.simulateClientResetError(forSession: realmURL) waitForExpectations(timeout: 10, handler: nil) XCTAssertNotNil(theError) XCTAssertTrue(theError!.code == SyncError.Code.clientResetError) let resetInfo = theError!.clientResetInfo() XCTAssertNotNil(resetInfo) XCTAssertTrue(resetInfo!.0.contains("io.realm.object-server-recovered-realms/recovered_realm")) XCTAssertNotNil(realm) } catch { XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))") } } func testClientResetManualInitiation() { do { let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL) var theError: SyncError? try autoreleasepool { let realm = try synchronouslyOpenRealm(url: realmURL, user: user) let ex = expectation(description: "Waiting for error handler to be called...") SyncManager.shared.errorHandler = { (error, session) in if let error = error as? SyncError { theError = error } else { XCTFail("Error \(error) was not a sync error. Something is wrong.") } ex.fulfill() } user.simulateClientResetError(forSession: realmURL) waitForExpectations(timeout: 10, handler: nil) XCTAssertNotNil(theError) XCTAssertNotNil(realm) } let (path, errorToken) = theError!.clientResetInfo()! XCTAssertFalse(FileManager.default.fileExists(atPath: path)) SyncSession.immediatelyHandleError(errorToken) XCTAssertTrue(FileManager.default.fileExists(atPath: path)) } catch { XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))") } } // MARK: - Progress notifiers func testStreamingDownloadNotifier() { let bigObjectCount = 2 do { var callCount = 0 var transferred = 0 var transferrable = 0 let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL) let realm = try synchronouslyOpenRealm(url: realmURL, user: user) if isParent { let session = realm.syncSession XCTAssertNotNil(session) let ex = expectation(description: "streaming-downloads-expectation") var hasBeenFulfilled = false let token = session!.addProgressNotification(for: .download, mode: .reportIndefinitely) { p in callCount += 1 XCTAssert(p.transferredBytes >= transferred) XCTAssert(p.transferrableBytes >= transferrable) transferred = p.transferredBytes transferrable = p.transferrableBytes if p.transferredBytes > 0 && p.isTransferComplete && !hasBeenFulfilled { ex.fulfill() hasBeenFulfilled = true } } // Wait for the child process to upload all the data. executeChild() waitForExpectations(timeout: 10.0, handler: nil) token!.invalidate() XCTAssert(callCount > 1) XCTAssert(transferred >= transferrable) } else { try realm.write { for _ in 0..= transferred) XCTAssert(p.transferrableBytes >= transferrable) transferred = p.transferredBytes transferrable = p.transferrableBytes if p.transferredBytes > 0 && p.isTransferComplete { ex.fulfill() } } waitForExpectations(timeout: 10.0, handler: nil) ex = expectation(description: "write transaction upload") try realm.write { for _ in 0..= transferrable) } catch { XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))") } } // MARK: - Download Realm func testDownloadRealm() { let bigObjectCount = 2 do { let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL) if isParent { // Wait for the child process to upload everything. executeChild() let ex = expectation(description: "download-realm") let config = user.configuration(realmURL: realmURL, fullSynchronization: true) let pathOnDisk = ObjectiveCSupport.convert(object: config).pathOnDisk XCTAssertFalse(FileManager.default.fileExists(atPath: pathOnDisk)) Realm.asyncOpen(configuration: config) { realm, error in XCTAssertNil(error) self.checkCount(expected: bigObjectCount, realm!, SwiftHugeSyncObject.self) ex.fulfill() } func fileSize(path: String) -> Int { if let attr = try? FileManager.default.attributesOfItem(atPath: path) { return attr[.size] as! Int } return 0 } XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk)) waitForExpectations(timeout: 10.0, handler: nil) XCTAssertGreaterThan(fileSize(path: pathOnDisk), 0) XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk)) } else { let realm = try synchronouslyOpenRealm(url: realmURL, user: user) // Write lots of data to the Realm, then wait for it to be uploaded. try realm.write { for _ in 0..