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.

365 lines
17 KiB

////////////////////////////////////////////////////////////////////////////
//
// Copyright 2019 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
class TreeObject: Object {
@objc dynamic var value: Int = 0
@objc dynamic var parent: TreeObject?
let children = LinkingObjects(fromType: TreeObject.self, property: "parent")
}
class SwiftPartialSyncTests: SwiftSyncTestCase {
func populateTestRealm(_ username: String) {
autoreleasepool {
let credentials = SyncCredentials.usernamePassword(username: username, password: "a", register: true)
let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
try! realm.write {
realm.add(SwiftPartialSyncObjectA(number: 0, string: "realm"))
realm.add(SwiftPartialSyncObjectA(number: 1, string: ""))
realm.add(SwiftPartialSyncObjectA(number: 2, string: ""))
realm.add(SwiftPartialSyncObjectA(number: 3, string: ""))
realm.add(SwiftPartialSyncObjectA(number: 4, string: "realm"))
realm.add(SwiftPartialSyncObjectA(number: 5, string: "sync"))
realm.add(SwiftPartialSyncObjectA(number: 6, string: "partial"))
realm.add(SwiftPartialSyncObjectA(number: 7, string: "partial"))
realm.add(SwiftPartialSyncObjectA(number: 8, string: "partial"))
realm.add(SwiftPartialSyncObjectA(number: 9, string: "partial"))
realm.add(SwiftPartialSyncObjectB(number: 0, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 1, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 2, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 3, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 4, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 5, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 6, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 7, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 8, firstString: "", secondString: ""))
realm.add(SwiftPartialSyncObjectB(number: 9, firstString: "", secondString: ""))
}
waitForUploads(for: realm)
}
}
func waitForState<T>(_ subscription: SyncSubscription<T>, _ desiredState: SyncSubscriptionState) {
let ex = expectation(description: "Waiting for state \(desiredState)")
let token = subscription.observe(\.state, options: .initial) { state in
if state == desiredState {
ex.fulfill()
}
}
waitForExpectations(timeout: 20.0)
token.invalidate()
}
func waitForError<T>(_ subscription: SyncSubscription<T>) {
let ex = expectation(description: "Waiting for error state")
let token = subscription.observe(\.state, options: .initial) { state in
if case .error(_) = state {
ex.fulfill()
}
}
waitForExpectations(timeout: 20.0)
token.invalidate()
}
func testPartialSync() {
populateTestRealm(#function)
let credentials = SyncCredentials.usernamePassword(username: #function, password: "a")
let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
let results = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5")
let subscription = results.subscribe(named: "query")
XCTAssertEqual(subscription.state, .creating)
waitForState(subscription, .complete)
// Verify that we got what we're looking for
XCTAssertEqual(results.count, 4)
for object in results {
XCTAssertGreaterThan(object.number, 5)
XCTAssertEqual(object.string, "partial")
}
// And that we didn't get anything else.
XCTAssertEqual(realm.objects(SwiftPartialSyncObjectA.self).count, results.count)
XCTAssertTrue(realm.objects(SwiftPartialSyncObjectB.self).isEmpty)
// Re-subscribing to an existing named query may not report the query's state immediately,
// but it should report it eventually.
let subscription2 = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5").subscribe(named: "query")
waitForState(subscription2, .complete)
// Creating a subscription with the same name but different query should raise an error.
let subscription3 = realm.objects(SwiftPartialSyncObjectA.self).filter("number < 5").subscribe(named: "query")
waitForError(subscription3)
// Unsubscribing should move the subscription to the invalidated state.
subscription.unsubscribe()
waitForState(subscription, .invalidated)
}
func testPartialSyncLimit() {
populateTestRealm(#function)
let credentials = SyncCredentials.usernamePassword(username: #function, password: "a")
let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
let results = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5")
waitForState(results.subscribe(named: "query", limit: 1), .complete)
XCTAssertEqual(results.count, 1)
XCTAssertEqual(realm.objects(SwiftPartialSyncObjectA.self).count, 1)
if let object = results.first {
XCTAssertGreaterThan(object.number, 5)
XCTAssertEqual(object.string, "partial")
}
let results2 = realm.objects(SwiftPartialSyncObjectA.self).sorted(byKeyPath: "number", ascending: false)
waitForState(results2.subscribe(named: "query2", limit: 2), .complete)
XCTAssertEqual(results2.count, 3)
XCTAssertEqual(realm.objects(SwiftPartialSyncObjectA.self).count, 3)
for object in results2 {
XCTAssertTrue(object.number == 6 || object.number >= 8,
"\(object.number) == 6 || \(object.number) >= 8")
XCTAssertEqual(object.string, "partial")
}
waitForState(results2.subscribe(named: "query2", limit: 1, update: true), .complete)
XCTAssertEqual(results2.count, 2)
XCTAssertEqual(realm.objects(SwiftPartialSyncObjectA.self).count, 2)
for object in results2 {
XCTAssertTrue(object.number == 6 || object.number == 9,
"\(object.number) == 6 || \(object.number) == 9")
XCTAssertEqual(object.string, "partial")
}
}
func testPartialSyncSubscriptions() {
let credentials = SyncCredentials.usernamePassword(username: #function, password: "a", register: true)
let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
XCTAssertEqual(realm.subscriptions().count, 0)
XCTAssertNil(realm.subscription(named: "query"))
let subscription = realm.objects(SwiftPartialSyncObjectA.self).filter("number > 5").subscribe(named: "query")
XCTAssertEqual(realm.subscriptions().count, 0)
XCTAssertNil(realm.subscription(named: "query"))
waitForState(subscription, .complete)
XCTAssertEqual(realm.subscriptions().count, 1)
let sub2 = realm.subscriptions().first!
XCTAssertEqual(sub2.name, "query")
XCTAssertEqual(sub2.state, .complete)
let sub3 = realm.subscription(named: "query")!
XCTAssertEqual(sub3.name, "query")
XCTAssertEqual(sub3.state, .complete)
for sub in realm.subscriptions() {
XCTAssertEqual(sub.name, "query")
XCTAssertEqual(sub.state, .complete)
}
XCTAssertNil(realm.subscription(named: "not query"))
}
func testSubscriptionPropertyUpdating() {
let credentials = SyncCredentials.usernamePassword(username: #function, password: "a", register: true)
let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
// Create the initial subscription
let objects = realm.objects(SwiftPartialSyncObjectA.self)
let sub1 = objects.filter("number > 5").subscribe(named: "query")
XCTAssertEqual(sub1.name, "query")
XCTAssertNil(sub1.query)
XCTAssertNotNil(sub1.createdAt)
XCTAssertNotNil(sub1.updatedAt)
XCTAssertEqual(sub1.createdAt, sub1.updatedAt)
XCTAssertNil(sub1.expiresAt)
XCTAssertNil(sub1.timeToLive)
let createdAt = sub1.createdAt!
// Verify that all of the properties are correct on both the returned
// subscription object and the one fetched from the Realm
waitForState(sub1, .complete)
XCTAssertEqual(sub1.name, "query")
XCTAssertEqual(sub1.query, "number > 5")
XCTAssertNotNil(sub1.createdAt)
XCTAssertNotNil(sub1.updatedAt)
XCTAssertEqual(sub1.createdAt, sub1.updatedAt)
XCTAssertGreaterThan(sub1.createdAt!, createdAt)
XCTAssertNil(sub1.expiresAt)
XCTAssertNil(sub1.timeToLive)
let sub2 = realm.subscriptions().first!
XCTAssertEqual(sub2.name, "query")
XCTAssertEqual(sub2.query, "number > 5")
XCTAssertNotNil(sub2.createdAt)
XCTAssertNotNil(sub2.updatedAt)
XCTAssertEqual(sub2.createdAt, sub2.updatedAt)
XCTAssertGreaterThan(sub2.createdAt!, createdAt)
XCTAssertNil(sub2.expiresAt)
XCTAssertNil(sub2.timeToLive)
// Update query and verify that propagates
waitForState(objects.filter("number > 6").subscribe(named: "query", update: true),
.complete)
XCTAssertEqual(sub1.name, "query")
XCTAssertEqual(sub1.query, "number > 6")
XCTAssertNotNil(sub1.createdAt)
XCTAssertNotNil(sub1.updatedAt)
XCTAssertGreaterThan(sub1.updatedAt!, sub1.createdAt!)
XCTAssertNil(sub1.expiresAt)
XCTAssertNil(sub1.timeToLive)
XCTAssertEqual(sub2.name, "query")
XCTAssertEqual(sub2.query, "number > 6")
XCTAssertNotNil(sub2.createdAt)
XCTAssertNotNil(sub2.updatedAt)
XCTAssertGreaterThan(sub2.updatedAt!, sub2.createdAt!)
XCTAssertNil(sub2.expiresAt)
XCTAssertNil(sub2.timeToLive)
// Update TTL and verify that propagates
waitForState(objects.filter("number > 6").subscribe(named: "query", update: true, timeToLive: 10.0),
.complete)
XCTAssertEqual(sub1.name, "query")
XCTAssertEqual(sub1.query, "number > 6")
XCTAssertNotNil(sub1.createdAt)
XCTAssertNotNil(sub1.updatedAt)
XCTAssertGreaterThan(sub1.updatedAt!, sub1.createdAt!)
XCTAssertEqual(sub1.updatedAt!.addingTimeInterval(10.0), sub1.expiresAt!)
XCTAssertEqual(sub1.timeToLive, 10.0)
XCTAssertEqual(sub2.name, "query")
XCTAssertEqual(sub2.query, "number > 6")
XCTAssertNotNil(sub2.createdAt)
XCTAssertNotNil(sub2.updatedAt)
XCTAssertGreaterThan(sub2.updatedAt!, sub2.createdAt!)
XCTAssertEqual(sub2.updatedAt!.addingTimeInterval(10.0), sub2.expiresAt!)
XCTAssertEqual(sub2.timeToLive, 10.0)
// Disable TTL and verify that propagates
waitForState(objects.filter("number > 6").subscribe(named: "query", update: true, timeToLive: nil),
.complete)
XCTAssertEqual(sub1.name, "query")
XCTAssertEqual(sub1.query, "number > 6")
XCTAssertNotNil(sub1.createdAt)
XCTAssertNotNil(sub1.updatedAt)
XCTAssertGreaterThan(sub1.updatedAt!, sub1.createdAt!)
XCTAssertNil(sub1.expiresAt)
XCTAssertNil(sub1.timeToLive)
XCTAssertEqual(sub2.name, "query")
XCTAssertEqual(sub2.query, "number > 6")
XCTAssertNotNil(sub2.createdAt)
XCTAssertNotNil(sub2.updatedAt)
XCTAssertGreaterThan(sub2.updatedAt!, sub2.createdAt!)
XCTAssertNil(sub2.expiresAt)
XCTAssertNil(sub2.timeToLive)
}
func testQueryingSubscriptions() {
let credentials = SyncCredentials.usernamePassword(username: #function, password: "a", register: true)
let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
// Verify that we can construct queries using the exposed property names
// Validation that the queries produce the correct results is covered by
// the obj-c tests
_ = realm.subscriptions().filter("name = 'a'")
_ = realm.subscriptions().filter("query = 'a'")
_ = realm.subscriptions().filter("createdAt > %@", Date())
_ = realm.subscriptions().filter("updatedAt > %@", Date())
_ = realm.subscriptions().filter("expiresAt > %@", Date())
_ = realm.subscriptions().filter("timeToLive = 5")
}
func testIncludeLinkingObjects() {
let credentials = SyncCredentials.usernamePassword(username: #function, password: "a", register: true)
let user = try! synchronouslyLogInUser(for: credentials, server: authURL)
let realm = try! synchronouslyOpenRealm(configuration: user.configuration())
// 0
// / | \
// 1 5 9
// / | \ / | \ / | \
// 2 3 4 6 7 8 10 11 12
try! realm.write {
let root = realm.create(TreeObject.self, value: [0])
for i in 0..<3 {
let child = realm.create(TreeObject.self, value: [1 + i * 4, root])
for j in 0..<3 {
_ = realm.create(TreeObject.self, value: [2 + i * 4 + j, child])
}
}
}
let objects = realm.objects(TreeObject.self)
// root only
waitForState(objects.filter("value = 0").subscribe(named: "query", update: true),
.complete)
XCTAssertEqual(objects.count, 1)
// root and children
waitForState(objects.filter("value = 0").subscribe(named: "query", update: true, includingLinkingObjects: ["children"]),
.complete)
XCTAssertEqual(objects.count, 4)
XCTAssertEqual(Set(objects.value(forKey: "value")! as! [Int]), Set([0, 1, 5, 9]))
// root, children and grandchildren
waitForState(objects.filter("value = 0").subscribe(named: "query", update: true, includingLinkingObjects: ["children", "children.children"]),
.complete)
XCTAssertEqual(objects.count, 13)
XCTAssertEqual(Set(objects.value(forKey: "value")! as! [Int]),
Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]))
// root and grandchildren pulls in children
waitForState(objects.filter("value = 0").subscribe(named: "query", update: true, includingLinkingObjects: [ "children.children"]),
.complete)
XCTAssertEqual(objects.count, 13)
XCTAssertEqual(Set(objects.value(forKey: "value")! as! [Int]),
Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]))
// one specific child and that child's children (plus root since it's a forward link)
waitForState(objects.filter("value = 5").subscribe(named: "query", update: true, includingLinkingObjects: ["children"]),
.complete)
XCTAssertEqual(objects.count, 5)
XCTAssertEqual(Set(objects.value(forKey: "value")! as! [Int]),
Set([0, 5, 6, 7, 8]))
// one specific grandchild
waitForState(objects.filter("value = 12").subscribe(named: "query", update: true, includingLinkingObjects: ["children"]),
.complete)
XCTAssertEqual(objects.count, 3)
XCTAssertEqual(Set(objects.value(forKey: "value")! as! [Int]),
Set([0, 9, 12]))
// one specific grandchild and all children via links off that grandchild
waitForState(objects.filter("value = 12").subscribe(named: "query", update: true, includingLinkingObjects: ["parent.parent.children"]),
.complete)
XCTAssertEqual(objects.count, 5)
XCTAssertEqual(Set(objects.value(forKey: "value")! as! [Int]),
Set([0, 1, 5, 9, 12]))
}
}