SwiftObjectServerTests.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. import XCTest
  19. import RealmSwift
  20. // Used by testOfflineClientReset
  21. // The naming here is nonstandard as the sync-1.x.realm test file comes from the .NET unit tests.
  22. // swiftlint:disable identifier_name
  23. @objc(Person)
  24. class Person: Object {
  25. @objc dynamic var FirstName: String?
  26. @objc dynamic var LastName: String?
  27. override class func shouldIncludeInDefaultSchema() -> Bool { return false }
  28. }
  29. class SwiftObjectServerTests: SwiftSyncTestCase {
  30. /// It should be possible to successfully open a Realm configured for sync.
  31. func testBasicSwiftSync() {
  32. let url = URL(string: "realm://127.0.0.1:9080/~/testBasicSync")!
  33. do {
  34. let user = try synchronouslyLogInUser(for: basicCredentials(register: true), server: authURL)
  35. let realm = try synchronouslyOpenRealm(url: url, user: user)
  36. XCTAssert(realm.isEmpty, "Freshly synced Realm was not empty...")
  37. } catch {
  38. XCTFail("Got an error: \(error)")
  39. }
  40. }
  41. /// If client B adds objects to a Realm, client A should see those new objects.
  42. func testSwiftAddObjects() {
  43. do {
  44. let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
  45. let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
  46. if isParent {
  47. waitForDownloads(for: realm)
  48. checkCount(expected: 0, realm, SwiftSyncObject.self)
  49. executeChild()
  50. waitForDownloads(for: realm)
  51. checkCount(expected: 3, realm, SwiftSyncObject.self)
  52. } else {
  53. // Add objects
  54. try realm.write {
  55. realm.add(SwiftSyncObject(value: ["child-1"]))
  56. realm.add(SwiftSyncObject(value: ["child-2"]))
  57. realm.add(SwiftSyncObject(value: ["child-3"]))
  58. }
  59. waitForUploads(for: realm)
  60. checkCount(expected: 3, realm, SwiftSyncObject.self)
  61. }
  62. } catch {
  63. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  64. }
  65. }
  66. /// If client B removes objects from a Realm, client A should see those changes.
  67. func testSwiftDeleteObjects() {
  68. do {
  69. let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
  70. let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
  71. if isParent {
  72. try realm.write {
  73. realm.add(SwiftSyncObject(value: ["child-1"]))
  74. realm.add(SwiftSyncObject(value: ["child-2"]))
  75. realm.add(SwiftSyncObject(value: ["child-3"]))
  76. }
  77. waitForUploads(for: realm)
  78. checkCount(expected: 3, realm, SwiftSyncObject.self)
  79. executeChild()
  80. waitForDownloads(for: realm)
  81. checkCount(expected: 0, realm, SwiftSyncObject.self)
  82. } else {
  83. try realm.write {
  84. realm.deleteAll()
  85. }
  86. waitForUploads(for: realm)
  87. checkCount(expected: 0, realm, SwiftSyncObject.self)
  88. }
  89. } catch {
  90. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  91. }
  92. }
  93. #if swift(>=3.2)
  94. func testConnectionState() {
  95. let user = try! synchronouslyLogInUser(for: basicCredentials(register: true), server: authURL)
  96. let realm = try! synchronouslyOpenRealm(url: realmURL, user: user)
  97. let session = realm.syncSession!
  98. func wait(forState desiredState: SyncSession.ConnectionState) {
  99. let ex = expectation(description: "Wait for connection state: \(desiredState)")
  100. let token = session.observe(\SyncSession.connectionState, options: .initial) { s, _ in
  101. if s.connectionState == desiredState {
  102. ex.fulfill()
  103. }
  104. }
  105. waitForExpectations(timeout: 2.0)
  106. token.invalidate()
  107. }
  108. wait(forState: .connected)
  109. session.suspend()
  110. wait(forState: .disconnected)
  111. session.resume()
  112. wait(forState: .connecting)
  113. wait(forState: .connected)
  114. }
  115. #endif // Swift >= 3.2
  116. // MARK: - Client reset
  117. func testClientReset() {
  118. do {
  119. let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
  120. let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
  121. var theError: SyncError?
  122. let ex = expectation(description: "Waiting for error handler to be called...")
  123. SyncManager.shared.errorHandler = { (error, session) in
  124. if let error = error as? SyncError {
  125. theError = error
  126. } else {
  127. XCTFail("Error \(error) was not a sync error. Something is wrong.")
  128. }
  129. ex.fulfill()
  130. }
  131. user.simulateClientResetError(forSession: realmURL)
  132. waitForExpectations(timeout: 10, handler: nil)
  133. XCTAssertNotNil(theError)
  134. XCTAssertTrue(theError!.code == SyncError.Code.clientResetError)
  135. let resetInfo = theError!.clientResetInfo()
  136. XCTAssertNotNil(resetInfo)
  137. XCTAssertTrue(resetInfo!.0.contains("io.realm.object-server-recovered-realms/recovered_realm"))
  138. XCTAssertNotNil(realm)
  139. } catch {
  140. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  141. }
  142. }
  143. func testClientResetManualInitiation() {
  144. do {
  145. let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
  146. var theError: SyncError?
  147. try autoreleasepool {
  148. let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
  149. let ex = expectation(description: "Waiting for error handler to be called...")
  150. SyncManager.shared.errorHandler = { (error, session) in
  151. if let error = error as? SyncError {
  152. theError = error
  153. } else {
  154. XCTFail("Error \(error) was not a sync error. Something is wrong.")
  155. }
  156. ex.fulfill()
  157. }
  158. user.simulateClientResetError(forSession: realmURL)
  159. waitForExpectations(timeout: 10, handler: nil)
  160. XCTAssertNotNil(theError)
  161. XCTAssertNotNil(realm)
  162. }
  163. let (path, errorToken) = theError!.clientResetInfo()!
  164. XCTAssertFalse(FileManager.default.fileExists(atPath: path))
  165. SyncSession.immediatelyHandleError(errorToken)
  166. XCTAssertTrue(FileManager.default.fileExists(atPath: path))
  167. } catch {
  168. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  169. }
  170. }
  171. // MARK: - Progress notifiers
  172. func testStreamingDownloadNotifier() {
  173. let bigObjectCount = 2
  174. do {
  175. var callCount = 0
  176. var transferred = 0
  177. var transferrable = 0
  178. let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
  179. let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
  180. if isParent {
  181. let session = realm.syncSession
  182. XCTAssertNotNil(session)
  183. let ex = expectation(description: "streaming-downloads-expectation")
  184. var hasBeenFulfilled = false
  185. let token = session!.addProgressNotification(for: .download, mode: .reportIndefinitely) { p in
  186. callCount += 1
  187. XCTAssert(p.transferredBytes >= transferred)
  188. XCTAssert(p.transferrableBytes >= transferrable)
  189. transferred = p.transferredBytes
  190. transferrable = p.transferrableBytes
  191. if p.transferredBytes > 0 && p.isTransferComplete && !hasBeenFulfilled {
  192. ex.fulfill()
  193. hasBeenFulfilled = true
  194. }
  195. }
  196. // Wait for the child process to upload all the data.
  197. executeChild()
  198. waitForExpectations(timeout: 10.0, handler: nil)
  199. token!.invalidate()
  200. XCTAssert(callCount > 1)
  201. XCTAssert(transferred >= transferrable)
  202. } else {
  203. try realm.write {
  204. for _ in 0..<bigObjectCount {
  205. realm.add(SwiftHugeSyncObject())
  206. }
  207. }
  208. waitForUploads(for: realm)
  209. checkCount(expected: bigObjectCount, realm, SwiftHugeSyncObject.self)
  210. }
  211. } catch {
  212. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  213. }
  214. }
  215. func testStreamingUploadNotifier() {
  216. let bigObjectCount = 2
  217. do {
  218. var callCount = 0
  219. var transferred = 0
  220. var transferrable = 0
  221. let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
  222. let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
  223. let session = realm.syncSession
  224. XCTAssertNotNil(session)
  225. let ex = expectation(description: "streaming-uploads-expectation")
  226. var hasBeenFulfilled = false
  227. let token = session!.addProgressNotification(for: .upload, mode: .reportIndefinitely) { p in
  228. callCount += 1
  229. XCTAssert(p.transferredBytes >= transferred)
  230. XCTAssert(p.transferrableBytes >= transferrable)
  231. transferred = p.transferredBytes
  232. transferrable = p.transferrableBytes
  233. if p.transferredBytes > 0 && p.isTransferComplete && !hasBeenFulfilled {
  234. ex.fulfill()
  235. hasBeenFulfilled = true
  236. }
  237. }
  238. try realm.write {
  239. for _ in 0..<bigObjectCount {
  240. realm.add(SwiftHugeSyncObject())
  241. }
  242. }
  243. waitForExpectations(timeout: 10.0, handler: nil)
  244. token!.invalidate()
  245. XCTAssert(callCount > 1)
  246. XCTAssert(transferred >= transferrable)
  247. } catch {
  248. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  249. }
  250. }
  251. // MARK: - Download Realm
  252. func testDownloadRealm() {
  253. let bigObjectCount = 2
  254. do {
  255. let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
  256. if isParent {
  257. // Wait for the child process to upload everything.
  258. executeChild()
  259. let ex = expectation(description: "download-realm")
  260. let config = user.configuration(realmURL: realmURL, fullSynchronization: true)
  261. let pathOnDisk = ObjectiveCSupport.convert(object: config).pathOnDisk
  262. XCTAssertFalse(FileManager.default.fileExists(atPath: pathOnDisk))
  263. Realm.asyncOpen(configuration: config) { realm, error in
  264. XCTAssertNil(error)
  265. self.checkCount(expected: bigObjectCount, realm!, SwiftHugeSyncObject.self)
  266. ex.fulfill()
  267. }
  268. func fileSize(path: String) -> Int {
  269. if let attr = try? FileManager.default.attributesOfItem(atPath: path) {
  270. return attr[.size] as! Int
  271. }
  272. return 0
  273. }
  274. let sizeBefore = fileSize(path: pathOnDisk)
  275. autoreleasepool {
  276. // We have partial transaction logs but no data
  277. XCTAssertGreaterThan(sizeBefore, 0)
  278. XCTAssert(try! Realm(configuration: config).isEmpty)
  279. }
  280. XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk))
  281. waitForExpectations(timeout: 10.0, handler: nil)
  282. XCTAssertGreaterThan(fileSize(path: pathOnDisk), sizeBefore)
  283. XCTAssertFalse(RLMHasCachedRealmForPath(pathOnDisk))
  284. } else {
  285. let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
  286. // Write lots of data to the Realm, then wait for it to be uploaded.
  287. try realm.write {
  288. for _ in 0..<bigObjectCount {
  289. realm.add(SwiftHugeSyncObject())
  290. }
  291. }
  292. waitForUploads(for: realm)
  293. checkCount(expected: bigObjectCount, realm, SwiftHugeSyncObject.self)
  294. }
  295. } catch {
  296. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  297. }
  298. }
  299. // MARK: - Administration
  300. func testRetrieveUserInfo() {
  301. let adminUsername = "jyaku.swift"
  302. let nonAdminUsername = "meela.swift@realm.example.org"
  303. let password = "p"
  304. let server = SwiftObjectServerTests.authServerURL()
  305. // Create a non-admin user.
  306. _ = logInUser(for: .init(username: nonAdminUsername, password: password, register: true),
  307. server: server)
  308. // Create an admin user.
  309. let adminUser = createAdminUser(for: server, username: adminUsername)
  310. // Look up information about the non-admin user from the admin user.
  311. let ex = expectation(description: "Should be able to look up user information")
  312. adminUser.retrieveInfo(forUser: nonAdminUsername, identityProvider: .usernamePassword) { (userInfo, err) in
  313. XCTAssertNil(err)
  314. XCTAssertNotNil(userInfo)
  315. guard let userInfo = userInfo else {
  316. return
  317. }
  318. let account = userInfo.accounts.first!
  319. XCTAssertEqual(account.providerUserIdentity, nonAdminUsername)
  320. XCTAssertEqual(account.provider, Provider.usernamePassword)
  321. XCTAssertFalse(userInfo.isAdmin)
  322. ex.fulfill()
  323. }
  324. waitForExpectations(timeout: 10.0, handler: nil)
  325. }
  326. // MARK: - Authentication
  327. func testInvalidCredentials() {
  328. do {
  329. let username = "testInvalidCredentialsUsername"
  330. let credentials = SyncCredentials.usernamePassword(username: username,
  331. password: "THIS_IS_A_PASSWORD",
  332. register: true)
  333. _ = try synchronouslyLogInUser(for: credentials, server: authURL)
  334. // Now log in the same user, but with a bad password.
  335. let ex = expectation(description: "wait for user login")
  336. let credentials2 = SyncCredentials.usernamePassword(username: username, password: "NOT_A_VALID_PASSWORD")
  337. SyncUser.logIn(with: credentials2, server: authURL) { user, error in
  338. XCTAssertNil(user)
  339. XCTAssertTrue(error is SyncAuthError)
  340. let castError = error as! SyncAuthError
  341. XCTAssertEqual(castError.code, SyncAuthError.invalidCredential)
  342. ex.fulfill()
  343. }
  344. waitForExpectations(timeout: 2.0, handler: nil)
  345. } catch {
  346. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  347. }
  348. }
  349. // MARK: - User-specific functionality
  350. func testUserExpirationCallback() {
  351. do {
  352. let user = try synchronouslyLogInUser(for: basicCredentials(), server: authURL)
  353. // Set a callback on the user
  354. var blockCalled = false
  355. let ex = expectation(description: "Error callback should fire upon receiving an error")
  356. user.errorHandler = { (u, error) in
  357. XCTAssertEqual(u.identity, user.identity)
  358. XCTAssertEqual(error.code, .accessDeniedOrInvalidPath)
  359. blockCalled = true
  360. ex.fulfill()
  361. }
  362. // Screw up the token on the user.
  363. manuallySetRefreshToken(for: user, value: "not-a-real-token")
  364. // Try to open a Realm with the user; this will cause our errorHandler block defined above to be fired.
  365. XCTAssertFalse(blockCalled)
  366. _ = try immediatelyOpenRealm(url: realmURL, user: user)
  367. waitForExpectations(timeout: 10.0, handler: nil)
  368. XCTAssertEqual(user.state, .loggedOut)
  369. } catch {
  370. XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
  371. }
  372. }
  373. // MARK: - Offline client reset
  374. func testOfflineClientReset() {
  375. let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
  376. let sourceFileURL = Bundle(for: type(of: self)).url(forResource: "sync-1.x", withExtension: "realm")!
  377. let fileName = "\(UUID()).realm"
  378. let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
  379. try! FileManager.default.copyItem(at: sourceFileURL, to: fileURL)
  380. let syncConfig = RLMSyncConfiguration(user: user, realmURL: realmURL)
  381. syncConfig.customFileURL = fileURL
  382. let config = Realm.Configuration(syncConfiguration: ObjectiveCSupport.convert(object: syncConfig))
  383. do {
  384. _ = try Realm(configuration: config)
  385. } catch let e as Realm.Error where e.code == .incompatibleSyncedFile {
  386. var backupConfiguration = e.backupConfiguration
  387. XCTAssertNotNil(backupConfiguration)
  388. // Open the backup Realm with a schema subset since it was created using the schema from .NET's unit tests.
  389. backupConfiguration!.objectTypes = [Person.self]
  390. let backupRealm = try! Realm(configuration: backupConfiguration!)
  391. let people = backupRealm.objects(Person.self)
  392. XCTAssertEqual(people.count, 1)
  393. XCTAssertEqual(people[0].FirstName, "John")
  394. XCTAssertEqual(people[0].LastName, "Smith")
  395. // Verify that we can now successfully open the original synced Realm.
  396. _ = try! Realm(configuration: config)
  397. } catch {
  398. fatalError("Unexpected error: \(error)")
  399. }
  400. }
  401. // MARK: - Partial sync
  402. func populateTestRealm(_ username: String) {
  403. autoreleasepool {
  404. let credentials = SyncCredentials.usernamePassword(username: username, password: "a", register: true)
  405. let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
  406. let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
  407. try! realm.write {
  408. realm.add(SwiftPartialSyncObjectA(number: 0, string: "realm"))
  409. realm.add(SwiftPartialSyncObjectA(number: 1, string: ""))
  410. realm.add(SwiftPartialSyncObjectA(number: 2, string: ""))
  411. realm.add(SwiftPartialSyncObjectA(number: 3, string: ""))
  412. realm.add(SwiftPartialSyncObjectA(number: 4, string: "realm"))
  413. realm.add(SwiftPartialSyncObjectA(number: 5, string: "sync"))
  414. realm.add(SwiftPartialSyncObjectA(number: 6, string: "partial"))
  415. realm.add(SwiftPartialSyncObjectA(number: 7, string: "partial"))
  416. realm.add(SwiftPartialSyncObjectA(number: 8, string: "partial"))
  417. realm.add(SwiftPartialSyncObjectA(number: 9, string: "partial"))
  418. realm.add(SwiftPartialSyncObjectB(number: 0, firstString: "", secondString: ""))
  419. realm.add(SwiftPartialSyncObjectB(number: 1, firstString: "", secondString: ""))
  420. realm.add(SwiftPartialSyncObjectB(number: 2, firstString: "", secondString: ""))
  421. realm.add(SwiftPartialSyncObjectB(number: 3, firstString: "", secondString: ""))
  422. realm.add(SwiftPartialSyncObjectB(number: 4, firstString: "", secondString: ""))
  423. realm.add(SwiftPartialSyncObjectB(number: 5, firstString: "", secondString: ""))
  424. realm.add(SwiftPartialSyncObjectB(number: 6, firstString: "", secondString: ""))
  425. realm.add(SwiftPartialSyncObjectB(number: 7, firstString: "", secondString: ""))
  426. realm.add(SwiftPartialSyncObjectB(number: 8, firstString: "", secondString: ""))
  427. realm.add(SwiftPartialSyncObjectB(number: 9, firstString: "", secondString: ""))
  428. }
  429. waitForUploads(for: realm)
  430. }
  431. }
  432. func testPartialSync() {
  433. populateTestRealm(#function)
  434. let credentials = SyncCredentials.usernamePassword(username: #function, password: "a")
  435. let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
  436. let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
  437. let results = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5")
  438. let subscription = results.subscribe(named: "query")
  439. XCTAssertEqual(subscription.state, .creating)
  440. waitForState(subscription, .complete)
  441. // Verify that we got what we're looking for
  442. XCTAssertEqual(results.count, 4)
  443. for object in results {
  444. XCTAssertGreaterThan(object.number, 5)
  445. XCTAssertEqual(object.string, "partial")
  446. }
  447. // And that we didn't get anything else.
  448. XCTAssertEqual(realm.objects(SwiftPartialSyncObjectA.self).count, results.count)
  449. XCTAssertTrue(realm.objects(SwiftPartialSyncObjectB.self).isEmpty)
  450. // Re-subscribing to an existing named query may not report the query's state immediately,
  451. // but it should report it eventually.
  452. let subscription2 = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5").subscribe(named: "query")
  453. waitForState(subscription2, .complete)
  454. // Creating a subscription with the same name but different query should raise an error.
  455. let subscription3 = realm.objects(SwiftPartialSyncObjectA.self).filter("number < 5").subscribe(named: "query")
  456. waitForError(subscription3)
  457. // Unsubscribing should move the subscription to the invalidated state.
  458. subscription.unsubscribe()
  459. waitForState(subscription, .invalidated)
  460. }
  461. func testPartialSyncLimit() {
  462. populateTestRealm(#function)
  463. let credentials = SyncCredentials.usernamePassword(username: #function, password: "a")
  464. let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
  465. let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
  466. let results = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5")
  467. waitForState(results.subscribe(named: "query", limit: 1), .complete)
  468. XCTAssertEqual(results.count, 1)
  469. XCTAssertEqual(realm.objects(SwiftPartialSyncObjectA.self).count, 1)
  470. if let object = results.first {
  471. XCTAssertGreaterThan(object.number, 5)
  472. XCTAssertEqual(object.string, "partial")
  473. }
  474. let results2 = realm.objects(SwiftPartialSyncObjectA.self).sorted(byKeyPath: "number", ascending: false)
  475. waitForState(results2.subscribe(named: "query2", limit: 2), .complete)
  476. XCTAssertEqual(results2.count, 3)
  477. XCTAssertEqual(realm.objects(SwiftPartialSyncObjectA.self).count, 3)
  478. for object in results2 {
  479. XCTAssertTrue(object.number == 6 || object.number >= 8,
  480. "\(object.number) == 6 || \(object.number) >= 8")
  481. XCTAssertEqual(object.string, "partial")
  482. }
  483. }
  484. func testPartialSyncSubscriptions() {
  485. let credentials = SyncCredentials.usernamePassword(username: #function, password: "a", register: true)
  486. let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
  487. let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
  488. XCTAssertEqual(realm.subscriptions().count, 0)
  489. XCTAssertNil(realm.subscription(named: "query"))
  490. let subscription = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5").subscribe(named: "query")
  491. XCTAssertEqual(realm.subscriptions().count, 0)
  492. XCTAssertNil(realm.subscription(named: "query"))
  493. waitForState(subscription, .complete)
  494. XCTAssertEqual(realm.subscriptions().count, 1)
  495. let sub2 = realm.subscriptions().first!
  496. XCTAssertEqual(sub2.name, "query")
  497. XCTAssertEqual(sub2.state, .complete)
  498. let sub3 = realm.subscription(named: "query")!
  499. XCTAssertEqual(sub3.name, "query")
  500. XCTAssertEqual(sub3.state, .complete)
  501. for sub in realm.subscriptions() {
  502. XCTAssertEqual(sub.name, "query")
  503. XCTAssertEqual(sub.state, .complete)
  504. }
  505. XCTAssertNil(realm.subscription(named: "not query"))
  506. }
  507. // Partial sync subscriptions are only available in Swift 3.2 and newer.
  508. #if swift(>=3.2)
  509. func waitForState<T>(_ subscription: SyncSubscription<T>, _ desiredState: SyncSubscriptionState) {
  510. let ex = expectation(description: "Waiting for state \(desiredState)")
  511. let token = subscription.observe(\.state, options: .initial) { state in
  512. if state == desiredState {
  513. ex.fulfill()
  514. }
  515. }
  516. waitForExpectations(timeout: 20.0)
  517. token.invalidate()
  518. }
  519. func waitForError<T>(_ subscription: SyncSubscription<T>) {
  520. let ex = expectation(description: "Waiting for error state")
  521. let token = subscription.observe(\.state, options: .initial) { state in
  522. if case .error(_) = state {
  523. ex.fulfill()
  524. }
  525. }
  526. waitForExpectations(timeout: 20.0)
  527. token.invalidate()
  528. }
  529. #else
  530. func waitForState<T>(_ subscription: SyncSubscription<T>, _ desiredState: SyncSubscriptionState) {
  531. for _ in 0..<20 {
  532. if subscription.state == desiredState {
  533. return
  534. }
  535. RunLoop.current.run(until: Date().addingTimeInterval(1.0))
  536. }
  537. XCTFail("waitForState(\(subscription), \(desiredState)) timed out")
  538. }
  539. func waitForError<T>(_ subscription: SyncSubscription<T>) {
  540. for _ in 0..<20 {
  541. if case .error(_) = subscription.state {
  542. return
  543. }
  544. RunLoop.current.run(until: Date().addingTimeInterval(1.0))
  545. }
  546. XCTFail("waitForError(\(subscription)) timed out")
  547. }
  548. #endif // Swift >= 3.2
  549. // MARK: - Certificate Pinning
  550. func testSecureConnectionToLocalhostWithDefaultSecurity() {
  551. let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
  552. let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
  553. serverValidationPolicy: .system)
  554. let ex = expectation(description: "Waiting for error handler to be called")
  555. SyncManager.shared.errorHandler = { (error, session) in
  556. ex.fulfill()
  557. }
  558. _ = try! Realm(configuration: config)
  559. self.waitForExpectations(timeout: 4.0)
  560. }
  561. func testSecureConnectionToLocalhostWithValidationDisabled() {
  562. let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
  563. let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
  564. serverValidationPolicy: .none)
  565. SyncManager.shared.errorHandler = { (error, session) in
  566. XCTFail("Unexpected connection failure: \(error)")
  567. }
  568. let realm = try! Realm(configuration: config)
  569. self.waitForUploads(for: realm)
  570. }
  571. func testSecureConnectionToLocalhostWithPinnedCertificate() {
  572. let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
  573. let certURL = URL(string: #file)!
  574. .deletingLastPathComponent()
  575. .appendingPathComponent("certificates")
  576. .appendingPathComponent("localhost.cer")
  577. let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
  578. serverValidationPolicy: .pinCertificate(path: certURL))
  579. SyncManager.shared.errorHandler = { (error, session) in
  580. XCTFail("Unexpected connection failure: \(error)")
  581. }
  582. let realm = try! Realm(configuration: config)
  583. self.waitForUploads(for: realm)
  584. }
  585. func testSecureConnectionToLocalhostWithIncorrectPinnedCertificate() {
  586. let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
  587. let certURL = URL(string: #file)!
  588. .deletingLastPathComponent()
  589. .appendingPathComponent("certificates")
  590. .appendingPathComponent("localhost-other.cer")
  591. let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
  592. serverValidationPolicy: .pinCertificate(path: certURL))
  593. let ex = expectation(description: "Waiting for error handler to be called")
  594. SyncManager.shared.errorHandler = { (error, session) in
  595. ex.fulfill()
  596. }
  597. _ = try! Realm(configuration: config)
  598. self.waitForExpectations(timeout: 4.0)
  599. }
  600. }