An amazing project that generates micro reports from tournament results
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

493 lines
21 KiB

////////////////////////////////////////////////////////////////////////////
//
// 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..<bigObjectCount {
realm.add(SwiftHugeSyncObject())
}
}
waitForUploads(for: realm)
checkCount(expected: bigObjectCount, realm, SwiftHugeSyncObject.self)
}
} catch {
XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
}
}
func testStreamingUploadNotifier() {
let bigObjectCount = 2
do {
var transferred = 0
var transferrable = 0
let user = try synchronouslyLogInUser(for: basicCredentials(register: isParent), server: authURL)
let realm = try synchronouslyOpenRealm(url: realmURL, user: user)
let session = realm.syncSession
XCTAssertNotNil(session)
var ex = expectation(description: "initial upload")
let token = session!.addProgressNotification(for: .upload, mode: .reportIndefinitely) { p in
XCTAssert(p.transferredBytes >= 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..<bigObjectCount {
realm.add(SwiftHugeSyncObject())
}
}
waitForExpectations(timeout: 10.0, handler: nil)
token!.invalidate()
XCTAssert(transferred >= 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..<bigObjectCount {
realm.add(SwiftHugeSyncObject())
}
}
waitForUploads(for: realm)
checkCount(expected: bigObjectCount, realm, SwiftHugeSyncObject.self)
}
} catch {
XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
}
}
// MARK: - Administration
func testRetrieveUserInfo() {
let adminUsername = "jyaku.swift"
let nonAdminUsername = "meela.swift@realm.example.org"
let password = "p"
let server = SwiftObjectServerTests.authServerURL()
// Create a non-admin user.
_ = logInUser(for: .init(username: nonAdminUsername, password: password, register: true),
server: server)
// Create an admin user.
let adminUser = createAdminUser(for: server, username: adminUsername)
// Look up information about the non-admin user from the admin user.
let ex = expectation(description: "Should be able to look up user information")
adminUser.retrieveInfo(forUser: nonAdminUsername, identityProvider: .usernamePassword) { (userInfo, err) in
XCTAssertNil(err)
XCTAssertNotNil(userInfo)
guard let userInfo = userInfo else {
return
}
let account = userInfo.accounts.first!
XCTAssertEqual(account.providerUserIdentity, nonAdminUsername)
XCTAssertEqual(account.provider, Provider.usernamePassword)
XCTAssertFalse(userInfo.isAdmin)
ex.fulfill()
}
waitForExpectations(timeout: 10.0, handler: nil)
}
// MARK: - Authentication
func testInvalidCredentials() {
do {
let username = "testInvalidCredentialsUsername"
let credentials = SyncCredentials.usernamePassword(username: username,
password: "THIS_IS_A_PASSWORD",
register: true)
_ = try synchronouslyLogInUser(for: credentials, server: authURL)
// Now log in the same user, but with a bad password.
let ex = expectation(description: "wait for user login")
let credentials2 = SyncCredentials.usernamePassword(username: username, password: "NOT_A_VALID_PASSWORD")
SyncUser.logIn(with: credentials2, server: authURL) { user, error in
XCTAssertNil(user)
XCTAssertTrue(error is SyncAuthError)
let castError = error as! SyncAuthError
XCTAssertEqual(castError.code, SyncAuthError.invalidCredential)
ex.fulfill()
}
waitForExpectations(timeout: 2.0, handler: nil)
} catch {
XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
}
}
// MARK: - User-specific functionality
func testUserExpirationCallback() {
do {
let user = try synchronouslyLogInUser(for: basicCredentials(), server: authURL)
// Set a callback on the user
var blockCalled = false
let ex = expectation(description: "Error callback should fire upon receiving an error")
user.errorHandler = { (u, error) in
XCTAssertEqual(u.identity, user.identity)
XCTAssertEqual(error.code, .accessDeniedOrInvalidPath)
blockCalled = true
ex.fulfill()
}
// Screw up the token on the user.
manuallySetRefreshToken(for: user, value: "not-a-real-token")
// Try to open a Realm with the user; this will cause our errorHandler block defined above to be fired.
XCTAssertFalse(blockCalled)
_ = try immediatelyOpenRealm(url: realmURL, user: user)
waitForExpectations(timeout: 10.0, handler: nil)
XCTAssertEqual(user.state, .loggedOut)
} catch {
XCTFail("Got an error: \(error) (process: \(isParent ? "parent" : "child"))")
}
}
// MARK: - Offline client reset
func testOfflineClientReset() {
let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
let sourceFileURL = Bundle(for: type(of: self)).url(forResource: "sync-1.x", withExtension: "realm")!
let fileName = "\(UUID()).realm"
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
try! FileManager.default.copyItem(at: sourceFileURL, to: fileURL)
let syncConfig = RLMSyncConfiguration(user: user, realmURL: realmURL)
syncConfig.customFileURL = fileURL
let config = Realm.Configuration(syncConfiguration: ObjectiveCSupport.convert(object: syncConfig))
do {
_ = try Realm(configuration: config)
} catch let e as Realm.Error where e.code == .incompatibleSyncedFile {
var backupConfiguration = e.backupConfiguration
XCTAssertNotNil(backupConfiguration)
// Open the backup Realm with a schema subset since it was created using the schema from .NET's unit tests.
backupConfiguration!.objectTypes = [Person.self]
let backupRealm = try! Realm(configuration: backupConfiguration!)
let people = backupRealm.objects(Person.self)
XCTAssertEqual(people.count, 1)
XCTAssertEqual(people[0].FirstName, "John")
XCTAssertEqual(people[0].LastName, "Smith")
// Verify that we can now successfully open the original synced Realm.
_ = try! Realm(configuration: config)
} catch {
fatalError("Unexpected error: \(error)")
}
}
// MARK: - Certificate Pinning
func testSecureConnectionToLocalhostWithDefaultSecurity() {
let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
serverValidationPolicy: .system)
let ex = expectation(description: "Waiting for error handler to be called")
SyncManager.shared.errorHandler = { (error, session) in
ex.fulfill()
}
_ = try! Realm(configuration: config)
self.waitForExpectations(timeout: 4.0)
}
func testSecureConnectionToLocalhostWithValidationDisabled() {
let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
serverValidationPolicy: .none)
SyncManager.shared.errorHandler = { (error, session) in
XCTFail("Unexpected connection failure: \(error)")
}
let realm = try! Realm(configuration: config)
self.waitForUploads(for: realm)
}
func testSecureConnectionToLocalhostWithPinnedCertificate() {
let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
let certURL = URL(string: #file)!
.deletingLastPathComponent()
.appendingPathComponent("certificates")
.appendingPathComponent("localhost.cer")
let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
serverValidationPolicy: .pinCertificate(path: certURL))
SyncManager.shared.errorHandler = { (error, session) in
XCTFail("Unexpected connection failure: \(error)")
}
let realm = try! Realm(configuration: config)
self.waitForUploads(for: realm)
}
func testSecureConnectionToLocalhostWithIncorrectPinnedCertificate() {
let user = try! synchronouslyLogInUser(for: basicCredentials(), server: authURL)
let certURL = URL(string: #file)!
.deletingLastPathComponent()
.appendingPathComponent("certificates")
.appendingPathComponent("localhost-other.cer")
let config = user.configuration(realmURL: URL(string: "realms://localhost:9443/~/default"),
serverValidationPolicy: .pinCertificate(path: certURL))
let ex = expectation(description: "Waiting for error handler to be called")
SyncManager.shared.errorHandler = { (error, session) in
ex.fulfill()
}
_ = try! Realm(configuration: config)
self.waitForExpectations(timeout: 4.0)
}
}