diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index 00060b200a..651fd31ac9 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -694,10 +694,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { // MARK: - App tests private func appConfig() -> AppConfiguration { -<<<<<<< HEAD - return AppConfiguration(baseURL: "http://localhost:9090", - localAppName: "auth-integration-tests", - localAppVersion: "20180301") + return AppConfiguration(baseURL: "http://localhost:9090") } func expectSuccess(_ result: Result) -> FieldType? { @@ -708,9 +705,6 @@ class SwiftObjectServerTests: SwiftSyncTestCase { XCTFail("unexpected error: \(error)") return nil } -======= - return AppConfiguration(baseURL: "http://localhost:9090") ->>>>>>> aea16af78a0bbfb2c964801becaecb9cade9335f } func testAppInit() { @@ -1339,1056 +1333,4 @@ class SwiftObjectServerTests: SwiftSyncTestCase { XCTAssertEqual((objectCol?["firstName"] as? String), "Morty") } } - -<<<<<<< HEAD -// XCTest doesn't care about the @available on the class and will try to run -// the tests even on older versions. Putting this check inside `defaultTestSuite` -// results in a warning about it being redundant due to the enclosing check, so -// it needs to be out of line. -func hasCombine() -> Bool { - if #available(OSX 10.15, watchOS 6.0, iOS 13.0, iOSApplicationExtension 13.0, OSXApplicationExtension 10.15, tvOS 13.0, *) { - return true - } - return false -} - -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -@objc(CombineObjectServerTests) -class CombineObjectServerTests: SwiftSyncTestCase { - override class var defaultTestSuite: XCTestSuite { - if hasCombine() { - return super.defaultTestSuite - } - return XCTestSuite(name: "\(type(of: self))") - } - - var subscriptions: Set = [] - - override func tearDown() { - subscriptions.forEach { $0.cancel() } - subscriptions = [] - super.tearDown() - } - - // swiftlint:disable multiple_closures_with_trailing_closure - func testWatchCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - - let watchEx1 = Locked(expectation(description: "Main thread watch")) - let watchEx2 = Locked(expectation(description: "Background thread watch")) - - collection.watch() - .onOpen { - watchEx1.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.global()) - .receive(on: DispatchQueue.global()) - .sink(receiveCompletion: { @Sendable _ in }) { @Sendable _ in - XCTAssertFalse(Thread.isMainThread) - watchEx1.wrappedValue.fulfill() - }.store(in: &subscriptions) - - collection.watch() - .onOpen { - watchEx2.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.main) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in }) { _ in - XCTAssertTrue(Thread.isMainThread) - watchEx2.wrappedValue.fulfill() - }.store(in: &subscriptions) - - for _ in 0..<3 { - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - watchEx1.wrappedValue = expectation(description: "Main thread watch") - watchEx2.wrappedValue = expectation(description: "Background thread watch") - collection.insertOne(document) { result in - if case .failure(let error) = result { - XCTFail("Failed to insert: \(error)") - } - } - } - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - } - - func testWatchCombineWithFilterIds() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - - let objIds = collection.insertMany([document, document2, document3, document4]).await(self) - let objectIds = objIds.map { $0.objectIdValue! } - - let watchEx1 = Locked(expectation(description: "Main thread watch")) - let watchEx2 = Locked(expectation(description: "Background thread watch")) - collection.watch(filterIds: [objectIds[0]]) - .onOpen { - watchEx1.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.main) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in }) { changeEvent in - XCTAssertTrue(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[0] { - watchEx1.wrappedValue.fulfill() - } - }.store(in: &subscriptions) - - collection.watch(filterIds: [objectIds[1]]) - .onOpen { - watchEx2.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.global()) - .receive(on: DispatchQueue.global()) - .sink(receiveCompletion: { _ in }) { @Sendable changeEvent in - XCTAssertFalse(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[1] { - watchEx2.wrappedValue.fulfill() - } - }.store(in: &subscriptions) - - for i in 0..<3 { - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - watchEx1.wrappedValue = expectation(description: "Main thread watch") - watchEx2.wrappedValue = expectation(description: "Background thread watch") - - let name: AnyBSON = .string("fido-\(i)") - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - } - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - } - - func testWatchCombineWithMatchFilter() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - - let objIds = collection.insertMany([document, document2, document3, document4]).await(self) - XCTAssertEqual(objIds.count, 4) - let objectIds = objIds.map { $0.objectIdValue! } - - let watchEx1 = Locked(expectation(description: "Main thread watch")) - let watchEx2 = Locked(expectation(description: "Background thread watch")) - collection.watch(matchFilter: ["fullDocument._id": AnyBSON.objectId(objectIds[0])]) - .onOpen { - watchEx1.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.main) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in }) { changeEvent in - XCTAssertTrue(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[0] { - watchEx1.wrappedValue.fulfill() - } - }.store(in: &subscriptions) - - collection.watch(matchFilter: ["fullDocument._id": AnyBSON.objectId(objectIds[1])]) - .onOpen { - watchEx2.wrappedValue.fulfill() - } - .subscribe(on: DispatchQueue.global()) - .receive(on: DispatchQueue.global()) - .sink(receiveCompletion: { _ in }) { @Sendable changeEvent in - XCTAssertFalse(Thread.isMainThread) - guard let doc = changeEvent.documentValue else { - return - } - - let objectId = doc["fullDocument"]??.documentValue!["_id"]??.objectIdValue! - if objectId == objectIds[1] { - watchEx2.wrappedValue.fulfill() - } - }.store(in: &subscriptions) - - for i in 0..<3 { - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - watchEx1.wrappedValue = expectation(description: "Main thread watch") - watchEx2.wrappedValue = expectation(description: "Background thread watch") - - let name: AnyBSON = .string("fido-\(i)") - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[0])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - collection.updateOneDocument(filter: ["_id": AnyBSON.objectId(objectIds[1])], - update: ["name": name, "breed": "king charles"]) { result in - if case .failure(let error) = result { - XCTFail("Failed to update: \(error)") - } - } - } - wait(for: [watchEx1.wrappedValue, watchEx2.wrappedValue], timeout: 60.0) - } - - // MARK: - Combine promises - - func testAppLoginCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - let loginEx = expectation(description: "Login user") - let appEx = expectation(description: "App changes triggered") - var triggered = 0 - app.objectWillChange.sink { _ in - triggered += 1 - if triggered == 2 { - appEx.fulfill() - } - }.store(in: &subscriptions) - - app.emailPasswordAuth.registerUser(email: email, password: password) - .flatMap { @Sendable in self.app.login(credentials: .emailPassword(email: email, password: password)) } - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { result in - if case let .failure(error) = result { - XCTFail("Should have completed login chain: \(error.localizedDescription)") - } - }, receiveValue: { user in - user.objectWillChange.sink { @Sendable user in - XCTAssert(!user.isLoggedIn) - loginEx.fulfill() - }.store(in: &self.subscriptions) - XCTAssertEqual(user.id, self.app.currentUser?.id) - user.logOut { _ in } // logout user and make sure it is observed - }) - .store(in: &subscriptions) - wait(for: [loginEx, appEx], timeout: 30.0) - XCTAssertEqual(self.app.allUsers.count, 1) - XCTAssertEqual(triggered, 2) - } - - func testAsyncOpenCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - app.emailPasswordAuth.registerUser(email: email, password: password) - .flatMap { @Sendable in self.app.login(credentials: .emailPassword(email: email, password: password)) } - .flatMap { @Sendable user in - Realm.asyncOpen(configuration: user.configuration(testName: #function)) - } - .await(self, timeout: 30.0) { realm in - try! realm.write { - realm.add(SwiftHugeSyncObject.create()) - realm.add(SwiftHugeSyncObject.create()) - } - let progressEx = self.expectation(description: "Should upload") - let token = realm.syncSession!.addProgressNotification(for: .upload, mode: .forCurrentlyOutstandingWork) { - if $0.isTransferComplete { - progressEx.fulfill() - } - } - self.wait(for: [progressEx], timeout: 30.0) - token?.invalidate() - } - - let chainEx = expectation(description: "Should chain realm login => realm async open") - let progressEx = expectation(description: "Should receive progress notification") - app.login(credentials: .anonymous) - .flatMap { @Sendable in - Realm.asyncOpen(configuration: $0.configuration(testName: #function)).onProgressNotification { - if $0.isTransferComplete { - progressEx.fulfill() - } - } - } - .expectValue(self, chainEx) { realm in - XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, 2) - }.store(in: &subscriptions) - wait(for: [chainEx, progressEx], timeout: 30.0) - } - - func testAsyncOpenStandaloneCombine() throws { - try autoreleasepool { - let realm = try Realm() - try realm.write { - (0..<10000).forEach { _ in realm.add(SwiftPerson(firstName: "Charlie", lastName: "Bucket")) } - } - } - - Realm.asyncOpen().await(self) { realm in - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 10000) - } - } - - func testDeleteUserCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - let appEx = expectation(description: "App changes triggered") - var triggered = 0 - app.objectWillChange.sink { _ in - triggered += 1 - if triggered == 2 { - appEx.fulfill() - } - }.store(in: &subscriptions) - - app.emailPasswordAuth.registerUser(email: email, password: password) - .flatMap { @Sendable in self.app.login(credentials: .emailPassword(email: email, password: password)) } - .flatMap { @Sendable in $0.delete() } - .await(self) - wait(for: [appEx], timeout: 30.0) - XCTAssertEqual(self.app.allUsers.count, 0) - XCTAssertEqual(triggered, 2) - } - - func testMongoCollectionInsertCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "tibetan mastiff"] - - collection.insertOne(document).await(self) - collection.insertMany([document, document2]) - .await(self) { objectIds in - XCTAssertEqual(objectIds.count, 2) - } - collection.find(filter: [:]) - .await(self) { findResult in - XCTAssertEqual(findResult.map({ $0["name"]??.stringValue }), ["fido", "fido", "rex"]) - } - } - - func testMongoCollectionFindCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "tibetan mastiff"] - let document3: Document = ["name": "rex", "breed": "tibetan mastiff", "coat": ["fawn", "brown", "white"]] - let findOptions = FindOptions(1, nil) - - collection.find(filter: [:], options: findOptions) - .await(self) { findResult in - XCTAssertEqual(findResult.count, 0) - } - collection.insertMany([document, document2, document3]).await(self) - collection.find(filter: [:]) - .await(self) { findResult in - XCTAssertEqual(findResult.map({ $0["name"]??.stringValue }), ["fido", "rex", "rex"]) - } - collection.find(filter: [:], options: findOptions) - .await(self) { findResult in - XCTAssertEqual(findResult.count, 1) - XCTAssertEqual(findResult[0]["name"]??.stringValue, "fido") - } - collection.find(filter: document3, options: findOptions) - .await(self) { findResult in - XCTAssertEqual(findResult.count, 1) - } - collection.findOneDocument(filter: document).await(self) - - collection.findOneDocument(filter: document, options: findOptions).await(self) - } - - func testMongoCollectionCountAndAggregateCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - - collection.insertMany([document]).await(self) - collection.aggregate(pipeline: [["$match": ["name": "fido"]], ["$group": ["_id": "$name"]]]) - .await(self) - collection.count(filter: document).await(self) { count in - XCTAssertEqual(count, 1) - } - collection.count(filter: document, limit: 1).await(self) { count in - XCTAssertEqual(count, 1) - } - } - - func testMongoCollectionDeleteOneCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - - collection.deleteOneDocument(filter: document).await(self) { count in - XCTAssertEqual(count, 0) - } - collection.insertMany([document, document2]).await(self) - collection.deleteOneDocument(filter: document).await(self) { count in - XCTAssertEqual(count, 1) - } - } - - func testMongoCollectionDeleteManyCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - - collection.deleteManyDocuments(filter: document).await(self) { count in - XCTAssertEqual(count, 0) - } - collection.insertMany([document, document2]).await(self) - collection.deleteManyDocuments(filter: ["breed": "cane corso"]).await(self) { count in - XCTAssertEqual(count, 2) - } - } - - func testMongoCollectionUpdateOneCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - let document5: Document = ["name": "bill", "breed": "great dane"] - - collection.insertMany([document, document2, document3, document4]).await(self) - collection.updateOneDocument(filter: document, update: document2).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 1) - XCTAssertEqual(updateResult.modifiedCount, 1) - XCTAssertNil(updateResult.documentId) - } - - collection.updateOneDocument(filter: document5, update: document2, upsert: true).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 0) - XCTAssertEqual(updateResult.modifiedCount, 0) - XCTAssertNotNil(updateResult.documentId) - } - } - - func testMongoCollectionUpdateManyCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - let document4: Document = ["name": "ted", "breed": "bullmastiff"] - let document5: Document = ["name": "bill", "breed": "great dane"] - - collection.insertMany([document, document2, document3, document4]).await(self) - collection.updateManyDocuments(filter: document, update: document2).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 1) - XCTAssertEqual(updateResult.modifiedCount, 1) - XCTAssertNil(updateResult.documentId) - } - collection.updateManyDocuments(filter: document5, update: document2, upsert: true).await(self) { updateResult in - XCTAssertEqual(updateResult.matchedCount, 0) - XCTAssertEqual(updateResult.modifiedCount, 0) - XCTAssertNotNil(updateResult.documentId) - } - } - - func testMongoCollectionFindAndUpdateCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - - collection.findOneAndUpdate(filter: document, update: document2).await(self) - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - collection.findOneAndUpdate(filter: document2, update: document3, options: options1).await(self) { updateResult in - guard let updateResult = updateResult else { - XCTFail("Should find") - return - } - XCTAssertEqual(updateResult["name"]??.stringValue, "john") - } - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - collection.findOneAndUpdate(filter: document, update: document2, options: options2).await(self) { updateResult in - guard let updateResult = updateResult else { - XCTFail("Should find") - return - } - XCTAssertEqual(updateResult["name"]??.stringValue, "rex") - } - } - - func testMongoCollectionFindAndReplaceCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - let document2: Document = ["name": "rex", "breed": "cane corso"] - let document3: Document = ["name": "john", "breed": "cane corso"] - - collection.findOneAndReplace(filter: document, replacement: document2).await(self) { updateResult in - XCTAssertNil(updateResult) - } - - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, true) - collection.findOneAndReplace(filter: document2, replacement: document3, options: options1).await(self) { updateResult in - guard let updateResult = updateResult else { - XCTFail("Should find") - return - } - XCTAssertEqual(updateResult["name"]??.stringValue, "john") - } - - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], true, false) - collection.findOneAndReplace(filter: document, replacement: document2, options: options2).await(self) { updateResult in - XCTAssertNil(updateResult) - } - } - - func testMongoCollectionFindAndDeleteCombine() throws { - let collection = try setupMongoCollection(for: Dog.self) - let document: Document = ["name": "fido", "breed": "cane corso"] - collection.insertMany([document]).await(self) - - collection.findOneAndDelete(filter: document).await(self) { updateResult in - XCTAssertNotNil(updateResult) - } - collection.findOneAndDelete(filter: document).await(self) { updateResult in - XCTAssertNil(updateResult) - } - - collection.insertMany([document]).await(self) - let options1 = FindOneAndModifyOptions(["name": 1], [["_id": 1]], false, false) - collection.findOneAndDelete(filter: document, options: options1).await(self) { deleteResult in - XCTAssertNotNil(deleteResult) - } - collection.findOneAndDelete(filter: document, options: options1).await(self) { deleteResult in - XCTAssertNil(deleteResult) - } - - collection.insertMany([document]).await(self) - let options2 = FindOneAndModifyOptions(["name": 1], [["_id": 1]]) - collection.findOneAndDelete(filter: document, options: options2).await(self) { deleteResult in - XCTAssertNotNil(deleteResult) - } - collection.findOneAndDelete(filter: document, options: options2).await(self) { deleteResult in - XCTAssertNil(deleteResult) - } - - collection.insertMany([document]).await(self) - collection.find(filter: [:]).await(self) { updateResult in - XCTAssertEqual(updateResult.count, 1) - } - } - - func testCallFunctionCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let credentials = Credentials.emailPassword(email: email, password: password) - app.login(credentials: credentials).await(self) { user in - XCTAssertNotNil(user) - } - - app.currentUser?.functions.sum([1, 2, 3, 4, 5]).await(self) { bson in - guard case let .int32(sum) = bson else { - XCTFail("Should be int32") - return - } - XCTAssertEqual(sum, 15) - } - - app.currentUser?.functions.updateUserData([["favourite_colour": "green", "apples": 10]]).await(self) { bson in - guard case let .bool(upd) = bson else { - XCTFail("Should be bool") - return - } - XCTAssertTrue(upd) - } - } - - func testAPIKeyAuthCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - - let user = app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - - let apiKey = user.apiKeysAuth.createAPIKey(named: "my-api-key").await(self) - user.apiKeysAuth.fetchAPIKey(apiKey.objectId).await(self) - user.apiKeysAuth.fetchAPIKeys().await(self) { userApiKeys in - XCTAssertEqual(userApiKeys.count, 1) - } - - user.apiKeysAuth.disableAPIKey(apiKey.objectId).await(self) - user.apiKeysAuth.enableAPIKey(apiKey.objectId).await(self) - user.apiKeysAuth.deleteAPIKey(apiKey.objectId).await(self) - } - - func testPushRegistrationCombine() { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - - app.emailPasswordAuth.registerUser(email: email, password: password).await(self) - app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - - let client = app.pushClient(serviceName: "gcm") - client.registerDevice(token: "some-token", user: app.currentUser!).await(self) - client.deregisterDevice(user: app.currentUser!).await(self) - } -} - -@available(macOS 12.0, *) -class AsyncAwaitObjectServerTests: SwiftSyncTestCase { - override class var defaultTestSuite: XCTestSuite { - // async/await is currently incompatible with thread sanitizer and will - // produce many false positives - // https://bugs.swift.org/browse/SR-15444 - if RLMThreadSanitizerEnabled() { - return XCTestSuite(name: "\(type(of: self))") - } - return super.defaultTestSuite - } - - func assertThrowsError(_ expression: @autoclosure () async throws -> FieldType, - file: StaticString = #filePath, line: UInt = #line, - _ errorHandler: (_ error: E) -> Void) async { - do { - _ = try await expression() - XCTFail("Expression should have thrown an error", file: file, line: line) - } catch let error as E { - errorHandler(error) - } catch { - XCTFail("Expected error of type \(E.self) but got \(error)") - } - } - - @MainActor func testAsyncOpenStandalone() async throws { - try autoreleasepool { - let configuration = Realm.Configuration(objectTypes: [SwiftPerson.self]) - let realm = try Realm(configuration: configuration) - try realm.write { - (0..<10).forEach { _ in realm.add(SwiftPerson(firstName: "Charlie", lastName: "Bucket")) } - } - } - let configuration = Realm.Configuration(objectTypes: [SwiftPerson.self]) - let realm = try await Realm(configuration: configuration) - XCTAssertEqual(realm.objects(SwiftPerson.self).count, 10) - } - - @MainActor func testAsyncOpenSync() async throws { - let user = try await self.app.login(credentials: basicCredentials()) - let realm = try await Realm(configuration: user.configuration(testName: #function)) - try realm.write { - realm.add(SwiftHugeSyncObject.create()) - realm.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm) - - let user2 = try await app.login(credentials: .anonymous) - let realm2 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .once) - XCTAssertEqual(realm2.objects(SwiftHugeSyncObject.self).count, 2) - } - - @MainActor func testAsyncOpenDownloadBehaviorNever() async throws { - // Populate the Realm on the server - let user1 = try await self.app.login(credentials: basicCredentials()) - let realm1 = try await Realm(configuration: user1.configuration(testName: #function)) - try realm1.write { - realm1.add(SwiftHugeSyncObject.create()) - realm1.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm1) - - // Should not have any objects as it just opens immediately without waiting - let user2 = try await app.login(credentials: .anonymous) - let realm2 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .never) - XCTAssertEqual(realm2.objects(SwiftHugeSyncObject.self).count, 0) - } - - @MainActor func testAsyncOpenDownloadBehaviorOnce() async throws { - // Populate the Realm on the server - let user1 = try await self.app.login(credentials: basicCredentials()) - let realm1 = try await Realm(configuration: user1.configuration(testName: #function)) - try realm1.write { - realm1.add(SwiftHugeSyncObject.create()) - realm1.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm1) - - // Should have the objects - let user2 = try await app.login(credentials: .anonymous) - let realm2 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .once) - XCTAssertEqual(realm2.objects(SwiftHugeSyncObject.self).count, 2) - realm2.syncSession?.suspend() - - // Add some more objects - try realm1.write { - realm1.add(SwiftHugeSyncObject.create()) - realm1.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm1) - - // Will not wait for the new objects to download - let realm3 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .once) - XCTAssertEqual(realm3.objects(SwiftHugeSyncObject.self).count, 2) - } - - @MainActor func testAsyncOpenDownloadBehaviorAlwaysWithCachedRealm() async throws { - // Populate the Realm on the server - let user1 = try await self.app.login(credentials: basicCredentials()) - let realm1 = try await Realm(configuration: user1.configuration(testName: #function)) - try realm1.write { - realm1.add(SwiftHugeSyncObject.create()) - realm1.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm1) - - // Should have the objects - let user2 = try await app.login(credentials: .anonymous) - let realm2 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .always) - XCTAssertEqual(realm2.objects(SwiftHugeSyncObject.self).count, 2) - realm2.syncSession?.suspend() - - // Add some more objects - try realm1.write { - realm1.add(SwiftHugeSyncObject.create()) - realm1.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm1) - - // Should wait for the new objects to download - let realm3 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .always) - XCTAssertEqual(realm3.objects(SwiftHugeSyncObject.self).count, 4) - } - - @MainActor func testAsyncOpenDownloadBehaviorAlwaysWithFreshRealm() async throws { - // Populate the Realm on the server - let user1 = try await self.app.login(credentials: basicCredentials()) - let realm1 = try await Realm(configuration: user1.configuration(testName: #function)) - try realm1.write { - realm1.add(SwiftHugeSyncObject.create()) - realm1.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm1) - - let user2 = try await app.login(credentials: .anonymous) - // Open in a Task so that the Realm is closed and re-opened later - _ = try await Task { - let realm2 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .always) - XCTAssertEqual(realm2.objects(SwiftHugeSyncObject.self).count, 2) - }.value - - // Add some more objects - try realm1.write { - realm1.add(SwiftHugeSyncObject.create()) - realm1.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm1) - - // Should wait for the new objects to download - let realm3 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .always) - XCTAssertEqual(realm3.objects(SwiftHugeSyncObject.self).count, 4) - } - - @MainActor func testDownloadPBSRealmCustomColumnNames() async throws { - // Populate the Realm on the server - let user1 = try await self.app.login(credentials: basicCredentials()) - let realm1 = try await Realm(configuration: user1.configuration(testName: #function)) - let objectId = ObjectId.generate() - let linkedObjectId = ObjectId.generate() - try realm1.write { - let object = SwiftCustomColumnObject() - object.id = objectId - object.binaryCol = "string".data(using: String.Encoding.utf8)! - let linkedObject = SwiftCustomColumnObject() - linkedObject.id = linkedObjectId - object.objectCol = linkedObject - realm1.add(object) - } - waitForUploads(for: realm1) - - // Should have the objects - let user2 = try await app.login(credentials: .anonymous) - let realm2 = try await Realm(configuration: user2.configuration(testName: #function), - downloadBeforeOpen: .once) - XCTAssertEqual(realm2.objects(SwiftCustomColumnObject.self).count, 2) - - let object = realm2.object(ofType: SwiftCustomColumnObject.self, forPrimaryKey: objectId) - XCTAssertNotNil(object) - XCTAssertEqual(object!.id, objectId) - XCTAssertEqual(object!.boolCol, true) - XCTAssertEqual(object!.intCol, 1) - XCTAssertEqual(object!.doubleCol, 1.1) - XCTAssertEqual(object!.stringCol, "string") - XCTAssertEqual(object!.binaryCol, "string".data(using: String.Encoding.utf8)!) - XCTAssertEqual(object!.dateCol, Date(timeIntervalSince1970: -1)) - XCTAssertEqual(object!.longCol, 1) - XCTAssertEqual(object!.decimalCol, Decimal128(1)) - XCTAssertEqual(object!.uuidCol, UUID(uuidString: "85d4fbee-6ec6-47df-bfa1-615931903d7e")!) - XCTAssertNil(object?.objectIdCol) - XCTAssertEqual(object!.objectCol!.id, linkedObjectId) - } - -#if swift(>=5.8) - // A custom executor which cancels the task after the requested number of - // invocations. This is a very naive executor which just synchronously - // invokes jobs, which generally is not a legal thing to do - final class CancellingExecutor: SerialExecutor, @unchecked Sendable { - private var remaining: Locked - private var pendingJob: UnownedJob? - var task: Task? { - didSet { - if let pendingJob = pendingJob { - self.pendingJob = nil - enqueue(pendingJob) - } - } - } - - init(cancelAfter: Int) { - remaining = Locked(cancelAfter) - } - - func enqueue(_ job: UnownedJob) { - // The body of the task is enqueued before the task variable is - // set, so we need to defer invoking the very first job - guard let task = task else { - precondition(pendingJob == nil) - pendingJob = job - return - } - - remaining.withLock { remaining in - if remaining == 0 { - task.cancel() - } - remaining -= 1 - - // S#-0392 exposes all the stuff we need for this in the public - // API (Which hopefully will arrive in Swift 5.9), but for now - // invoking a job requires some private things. - _swiftJobRun(job, self.asUnownedSerialExecutor()) - } - } - - func asUnownedSerialExecutor() -> UnownedSerialExecutor { - UnownedSerialExecutor(ordinary: self) - } - } - - // An actor that does nothing other than have a custom executor - actor CustomExecutorActor { - nonisolated let executor: UnownedSerialExecutor - init(_ executor: UnownedSerialExecutor) { - self.executor = executor - } - nonisolated var unownedExecutor: UnownedSerialExecutor { - executor - } - } - - @MainActor func testAsyncOpenTaskCancellation() async throws { - // Populate the Realm on the server - let user = try await self.app.login(credentials: basicCredentials()) - let configuration = user.configuration(testName: #function) - try await Task { @MainActor in - let realm = try await Realm(configuration: configuration) - try realm.write { - realm.add(SwiftHugeSyncObject.create()) - realm.add(SwiftHugeSyncObject.create()) - } - waitForUploads(for: realm) - }.value - - func isolatedOpen(_ actor: isolated CustomExecutorActor) async throws { - _ = try await Realm(configuration: configuration, actor: actor, downloadBeforeOpen: .always) - } - - // Try opening the Realm with the Task being cancelled at every possible - // point between executor invocations. This doesn't really test that - // cancellation is *correct*; just that cancellation never results in - // a hang or crash. - for i in 0 ..< .max { - RLMWaitForRealmToClose(configuration.fileURL!.path) - _ = try Realm.deleteFiles(for: configuration) - - let executor = CancellingExecutor(cancelAfter: i) - executor.task = Task { - try await isolatedOpen(.init(executor.asUnownedSerialExecutor())) - } - do { - try await executor.task!.value - break - } catch is CancellationError { - // pass - } catch { - XCTFail("Expected CancellationError but got \(error)") - } - } - - // Repeat the above, but with a cached Realm so that we hit that code path instead - let cachedRealm = try await Realm(configuration: configuration, downloadBeforeOpen: .always) - for i in 0 ..< .max { - let executor = CancellingExecutor(cancelAfter: i) - executor.task = Task { - try await isolatedOpen(.init(executor.asUnownedSerialExecutor())) - } - do { - try await executor.task!.value - break - } catch is CancellationError { - // pass - } catch { - XCTFail("Expected CancellationError but got \(error)") - } - } - cachedRealm.invalidate() - } -#endif - - func testCallResetPasswordAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - let auth = app.emailPasswordAuth - await assertThrowsError(try await auth.callResetPasswordFunction(email: email, - password: randomString(10), - args: [[:]])) { - assertAppError($0, .unknown, "failed to reset password for user \"\(email)\"") - } - } - - func testAppLinkUserAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - let syncUser = try await self.app.login(credentials: Credentials.anonymous) - - let credentials = Credentials.emailPassword(email: email, password: password) - let linkedUser = try await syncUser.linkUser(credentials: credentials) - XCTAssertEqual(linkedUser.id, app.currentUser?.id) - XCTAssertEqual(linkedUser.identities.count, 2) - } - - func testUserCallFunctionAsyncAwait() async throws { - let user = try await self.app.login(credentials: basicCredentials()) - guard case let .int32(sum) = try await user.functions.sum([1, 2, 3, 4, 5]) else { - return XCTFail("Should be int32") - } - XCTAssertEqual(sum, 15) - } - - // MARK: - Objective-C async await - func testPushRegistrationAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - _ = try await app.login(credentials: Credentials.emailPassword(email: email, password: password)) - - let client = app.pushClient(serviceName: "gcm") - try await client.registerDevice(token: "some-token", user: app.currentUser!) - try await client.deregisterDevice(user: app.currentUser!) - } - - func testEmailPasswordProviderClientAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - let auth = app.emailPasswordAuth - try await auth.registerUser(email: email, password: password) - - await assertThrowsError(try await auth.confirmUser("atoken", tokenId: "atokenid")) { - assertAppError($0, .badRequest, "invalid token data") - } - await assertThrowsError(try await auth.resendConfirmationEmail(email)) { - assertAppError($0, .userAlreadyConfirmed, "already confirmed") - } - await assertThrowsError(try await auth.retryCustomConfirmation(email)) { - assertAppError($0, .unknown, - "cannot run confirmation for \(email): automatic confirmation is enabled") - } - await assertThrowsError(try await auth.sendResetPasswordEmail("atoken")) { - assertAppError($0, .userNotFound, "user not found") - } - } - - func testUserAPIKeyProviderClientAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - let credentials = Credentials.emailPassword(email: email, password: password) - let syncUser = try await self.app.login(credentials: credentials) - let apiKey = try await syncUser.apiKeysAuth.createAPIKey(named: "my-api-key") - XCTAssertNotNil(apiKey) - - let fetchedApiKey = try await syncUser.apiKeysAuth.fetchAPIKey(apiKey.objectId) - XCTAssertNotNil(fetchedApiKey) - - let fetchedApiKeys = try await syncUser.apiKeysAuth.fetchAPIKeys() - XCTAssertNotNil(fetchedApiKeys) - XCTAssertEqual(fetchedApiKeys.count, 1) - - try await syncUser.apiKeysAuth.disableAPIKey(apiKey.objectId) - try await syncUser.apiKeysAuth.enableAPIKey(apiKey.objectId) - try await syncUser.apiKeysAuth.deleteAPIKey(apiKey.objectId) - - let newFetchedApiKeys = try await syncUser.apiKeysAuth.fetchAPIKeys() - XCTAssertNotNil(newFetchedApiKeys) - XCTAssertEqual(newFetchedApiKeys.count, 0) - } - - func testCustomUserDataAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - let user = try await self.app.login(credentials: .anonymous) - XCTAssertNotNil(user) - - _ = try await user.functions.updateUserData([ - ["favourite_colour": "green", "apples": 10] - ]) - - try await app.currentUser?.refreshCustomData() - XCTAssertEqual(app.currentUser?.customData["favourite_colour"], .string("green")) - XCTAssertEqual(app.currentUser?.customData["apples"], .int64(10)) - } - - func testDeleteUserAsyncAwait() async throws { - let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" - let password = randomString(10) - let credentials: Credentials = .emailPassword(email: email, password: password) - try await app.emailPasswordAuth.registerUser(email: email, password: password) - - let user = try await self.app.login(credentials: credentials) - XCTAssertNotNil(user) - - XCTAssertNotNil(app.currentUser) - try await user.delete() - - XCTAssertNil(app.currentUser) - XCTAssertEqual(app.allUsers.count, 0) - } -} - -======= ->>>>>>> aea16af78a0bbfb2c964801becaecb9cade9335f #endif // os(macOS) diff --git a/Realm/ObjectServerTests/SwiftSyncTestCase.swift b/Realm/ObjectServerTests/SwiftSyncTestCase.swift index d4e7440c37..def4633a37 100644 --- a/Realm/ObjectServerTests/SwiftSyncTestCase.swift +++ b/Realm/ObjectServerTests/SwiftSyncTestCase.swift @@ -115,9 +115,9 @@ open class SwiftSyncTestCase: RLMSyncTestCase { config = user.configuration(partitionValue: partitionValue) } let realm = try Realm(configuration: config) - if wait { +// if wait { waitForDownloads(for: realm) - } +// } return realm } public func openRealm(app: App? = nil, wait: Bool = true) throws -> Realm { @@ -184,7 +184,7 @@ open class SwiftSyncTestCase: RLMSyncTestCase { } } - public func checkCount(expected: Int, + public func checkCount(expected: Int, _ realm: Realm, _ type: FieldType.Type, file: StaticString = #file, @@ -218,7 +218,6 @@ open class SwiftSyncTestCase: RLMSyncTestCase { } public static let bigObjectCount = 2 -<<<<<<< HEAD public func populateRealm(user: User? = nil, partitionValue: FieldType) throws { try autoreleasepool { let user = try (user ?? logInUser(for: basicCredentials())) @@ -229,16 +228,16 @@ open class SwiftSyncTestCase: RLMSyncTestCase { for _ in 0..>>>>>> aea16af78a0bbfb2c964801becaecb9cade9335f } } } - // MARK: - Mongo Client public func setupMongoCollection(for type: ObjectBase.Type) throws -> MongoCollection {