diff --git a/.codecov.yml b/.codecov.yml index c82437a0e..3a8b437bb 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,7 +6,7 @@ coverage: status: patch: default: - target: 33 + target: 75 changes: false project: default: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1617ccb48..008234283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,25 @@ # Parse-Swift Changelog ### main +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.9.0...main) +* _Contributing to this repo? Add info about your change here to be included in the next release_ + +### 4.9.0 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.8.0...4.9.0) __New features__ +- Add methods for migrating users and installations from the Parse Objective-C SDK to the Swift SDK ([#391](https://github.com/parse-community/Parse-Swift/pull/391)), thanks to [Corey Baker](https://github.com/cbaker6). - Enable query caching by using GET instead of POST. GET is now used by default. To switch back to POST, set usingPostForQuery = true when initializing the SDK which will automatically disable all query caching ([#386](https://github.com/parse-community/Parse-Swift/pull/386)), thanks to [Corey Baker](https://github.com/cbaker6). __Improvements__ +- Add more details to error messages related when decoding errors occur ([#388](https://github.com/parse-community/Parse-Swift/pull/388)), thanks to [Daniel Blyth](https://github.com/dblythy). - Added discardableResult to allow developers to choose whether or not certain functions should return a result ([#385](https://github.com/parse-community/Parse-Swift/pull/385)), thanks to [Damian Van de Kauter](https://github.com/vdkdamian). -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.8.0...main) -* _Contributing to this repo? Add info about your change here to be included in the next release_ +__Fixes__ +- Ensure properties that are already saved ParseObject's are converted to Parse pointers when using saveAll ([#390](https://github.com/parse-community/Parse-Swift/pull/390)), thanks to [Corey Baker](https://github.com/cbaker6). + +### 4.8.0 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.7.0...4.8.0) __New features__ - Add ParseSpotify authentication ([#375](https://github.com/parse-community/Parse-Swift/pull/375)), thanks to [Ulaş Sancak](https://github.com/rocxteady). @@ -168,7 +178,7 @@ __Fixes__ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.3.0...2.3.1) __Fixes__ -- Fixed an issue where querying an object didn't dispatch to the proper queue which can cause app crashes ([#293](https://github.com/parse-community/Parse-Swift/pull/293)), thanks to [Corey Baker](https://github.com/cbaker6). +- Fixed an issue where querying an object did not dispatch to the proper queue which can cause app crashes ([#293](https://github.com/parse-community/Parse-Swift/pull/293)), thanks to [Corey Baker](https://github.com/cbaker6). ### 2.3.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.6...2.3.0) @@ -178,7 +188,7 @@ __New features__ - Add toCLLocation and toCLLocationCoordinate2D methods for easy conversion from a ParseGeoPoint object ([#287](https://github.com/parse-community/Parse-Swift/pull/287)), thanks to [Jayson Ng](https://github.com/jaysonng). __Fixes__ -- Fixed an issue where an annonymous couldn't be turned into a regular user using signup ([#291](https://github.com/parse-community/Parse-Swift/pull/291)), thanks to [Corey Baker](https://github.com/cbaker6). +- Fixed an issue where an annonymous could not be turned into a regular user using signup ([#291](https://github.com/parse-community/Parse-Swift/pull/291)), thanks to [Corey Baker](https://github.com/cbaker6). - The default ACL is now deleted from the keychain when a user is logged out. This previously caused an issue when logging out a user and logging in as a different user caused all objects to only have ACL permisions for the logged in user ([#291](https://github.com/parse-community/Parse-Swift/pull/291)), thanks to [Corey Baker](https://github.com/cbaker6). ### 2.2.6 @@ -225,7 +235,7 @@ __Improvements__ - Added ability to fetch ParsePointer using async/await ([#271](https://github.com/parse-community/Parse-Swift/pull/271)), thanks to [Corey Baker](https://github.com/cbaker6). __Fixes__ -- By default, don't use cache when fetching ParseObject's and ParseFile's. Developers can choose to fetch from cache if desired by passing the necessary option while fetching. Fixed a bug when the incorrect file location for a dowloaded ParseFile was being cached ([#272](https://github.com/parse-community/Parse-Swift/pull/272)), thanks to [Corey Baker](https://github.com/cbaker6). +- By default, do not use cache when fetching ParseObject's and ParseFile's. Developers can choose to fetch from cache if desired by passing the necessary option while fetching. Fixed a bug when the incorrect file location for a dowloaded ParseFile was being cached ([#272](https://github.com/parse-community/Parse-Swift/pull/272)), thanks to [Corey Baker](https://github.com/cbaker6). ### 2.1.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.0.3...2.1.0) @@ -306,13 +316,13 @@ __Improvements__ - (Breaking Change) Provide ParseObject property, emptyObject, that makes it easy to send only modified keys to the server. This change "might" be breaking depending on your implementation as it requires ParseObjects to now have an empty initializer, init() ([#243](https://github.com/parse-community/Parse-Swift/pull/243)), thanks to [Corey Baker](https://github.com/cbaker6). __Fixes__ -- ParseUser shouldn't send email if it hasn't been modified or else email verification is resent ([#241](https://github.com/parse-community/Parse-Swift/pull/241)), thanks to [Corey Baker](https://github.com/cbaker6). +- ParseUser should not send email if it has not been modified or else email verification is resent ([#241](https://github.com/parse-community/Parse-Swift/pull/241)), thanks to [Corey Baker](https://github.com/cbaker6). ### 1.9.10 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.9...1.9.10) __Fixes__ -- ParseInstallation can't be retreived from Keychain after the first fun ([#236](https://github.com/parse-community/Parse-Swift/pull/236)), thanks to [Corey Baker](https://github.com/cbaker6). +- ParseInstallation cannot be retreived from Keychain after the first fun ([#236](https://github.com/parse-community/Parse-Swift/pull/236)), thanks to [Corey Baker](https://github.com/cbaker6). ### 1.9.9 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.8...1.9.9) @@ -386,13 +396,13 @@ __New features__ __Improvements__ - Removed CommonCrypto and now uses encoded string as a hash for child ParseObjects across all OS's ([#184](https://github.com/parse-community/Parse-Swift/pull/184)), thanks to [Corey Baker](https://github.com/cbaker6). - All types now conform to CustomStringConvertible ([#185](https://github.com/parse-community/Parse-Swift/pull/185)), thanks to [Corey Baker](https://github.com/cbaker6). -- Setting limit = 0 of a query doesn't query the server and instead just returns empty or no results depending on the query ([#189](https://github.com/parse-community/Parse-Swift/pull/189)), thanks to [Corey Baker](https://github.com/cbaker6). +- Setting limit = 0 of a query does not query the server and instead just returns empty or no results depending on the query ([#189](https://github.com/parse-community/Parse-Swift/pull/189)), thanks to [Corey Baker](https://github.com/cbaker6). - ParseGeoPoint initializer now throws if geopoints are out-of-bounds instead of asserting ([#190](https://github.com/parse-community/Parse-Swift/pull/190)), thanks to [Corey Baker](https://github.com/cbaker6). -- Persist all properties of ParseUser and ParseInstallation to keychain so they can be accessed via current. Developers don't have to fetch the ParseUser or ParseInstlation after app restart anymore ([#191](https://github.com/parse-community/Parse-Swift/pull/191)), thanks to [Corey Baker](https://github.com/cbaker6). +- Persist all properties of ParseUser and ParseInstallation to keychain so they can be accessed via current. Developers do not have to fetch the ParseUser or ParseInstlation after app restart anymore ([#191](https://github.com/parse-community/Parse-Swift/pull/191)), thanks to [Corey Baker](https://github.com/cbaker6). __Fixes__ - Fixed a bug when signing up from a ParseUser instance resulted in custom keys not being persisted to the keychain ([#187](https://github.com/parse-community/Parse-Swift/pull/187)), thanks to [Corey Baker](https://github.com/cbaker6). -- Fixed a bug where countExplain query result wasn't returned as an array ([#189](https://github.com/parse-community/Parse-Swift/pull/189)), thanks to [Corey Baker](https://github.com/cbaker6). +- Fixed a bug where countExplain query result was not returned as an array ([#189](https://github.com/parse-community/Parse-Swift/pull/189)), thanks to [Corey Baker](https://github.com/cbaker6). - The query withinPolygon(key: String, points: [ParseGeoPoint]) now works correctly and sends an array of doubles instead of an array of GeoPoint's ([#190](https://github.com/parse-community/Parse-Swift/pull/190)), thanks to [Corey Baker](https://github.com/cbaker6). - Fixed a bug where the ParseEncoder incorrectly detects a circular dependency when two child objects are the same ([#194](https://github.com/parse-community/Parse-Swift/pull/194)), thanks to [Corey Baker](https://github.com/cbaker6). - Make sure all LiveQuery socket changes are received on the correct queue to prevent threading issues ([#195](https://github.com/parse-community/Parse-Swift/pull/195)), thanks to [Corey Baker](https://github.com/cbaker6). @@ -440,7 +450,7 @@ __Improvements__ - Append instead of replace when using query select, exclude, include, and fields ([#155](https://github.com/parse-community/Parse-Swift/pull/155)), thanks to [Corey Baker](https://github.com/cbaker6). __Fixes__ -- Transactions currently don't work when using MongoDB(postgres does work) on the parse-server. Internal use of transactions are disabled by default. If you want the Swift SDK to use transactions internally, you need to set isUsingTransactionsInternally=true when configuring the client. It is recommended not to use transactions if you are using MongoDB until it's fixed on the server ([#158](https://github.com/parse-community/Parse-Swift/pull/158)), thanks to [Corey Baker](https://github.com/cbaker6). +- Transactions currently do not work when using MongoDB(postgres does work) on the parse-server. Internal use of transactions are disabled by default. If you want the Swift SDK to use transactions internally, you need to set isUsingTransactionsInternally=true when configuring the client. It is recommended not to use transactions if you are using MongoDB until it is fixed on the server ([#158](https://github.com/parse-community/Parse-Swift/pull/158)), thanks to [Corey Baker](https://github.com/cbaker6). ### 1.8.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.7.2...1.8.0) @@ -462,7 +472,7 @@ __New features__ - Added ability to send context with object by specifying it within options ([#140](https://github.com/parse-community/Parse-Swift/pull/140)), thanks to [Corey Baker](https://github.com/cbaker6). __Fixes__ -- ParseFiles can't be updated from the client and will now throw an error if attempted. Instead another file should be created and the older file should be deleted by the developer. ([#144](https://github.com/parse-community/Parse-Swift/pull/144)), thanks to [Corey Baker](https://github.com/cbaker6). +- ParseFiles cannot be updated from the client and will now throw an error if attempted. Instead another file should be created and the older file should be deleted by the developer. ([#144](https://github.com/parse-community/Parse-Swift/pull/144)), thanks to [Corey Baker](https://github.com/cbaker6). - Fixed issue where Swift SDK prevented fetching of Parse objects when custom objectId was enabled ([#139](https://github.com/parse-community/Parse-Swift/pull/139)), thanks to [Corey Baker](https://github.com/cbaker6). __Improvements__ @@ -497,7 +507,7 @@ __Improvements__ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.4.0...1.5.0) __Improvements__ -- (Breaking Change) Aggregrate takes any Encodable type. Query planning methods are now: findExlpain, firstEplain, countExplain, etc. The distinct query now works. The client will also not throw an error anymore when attempting to delete a File and the masterKey isn't available. The developer will still need to configure the server to delete the file properly ([#122](https://github.com/parse-community/Parse-Swift/pull/122)), thanks to [Corey Baker](https://github.com/cbaker6). +- (Breaking Change) Aggregrate takes any Encodable type. Query planning methods are now: findExlpain, firstEplain, countExplain, etc. The distinct query now works. The client will also not throw an error anymore when attempting to delete a File and the masterKey is not available. The developer will still need to configure the server to delete the file properly ([#122](https://github.com/parse-community/Parse-Swift/pull/122)), thanks to [Corey Baker](https://github.com/cbaker6). ### 1.4.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.3.1...1.4.0) @@ -559,7 +569,7 @@ __New features__ - Add build support for Android ([#90](https://github.com/parse-community/Parse-Swift/pull/90)), thanks to [jt9897253](https://github.com/jt9897253). __Fixes__ -- There was another bug after a user first logs in anonymously and then becomes a real user. The authData sent to the server wasn't stripped, keep the user anonymous instead of making them a real user ([#100](https://github.com/parse-community/Parse-Swift/pull/100)), thanks to [Corey Baker](https://github.com/cbaker6). +- There was another bug after a user first logs in anonymously and then becomes a real user. The authData sent to the server was not stripped, keep the user anonymous instead of making them a real user ([#100](https://github.com/parse-community/Parse-Swift/pull/100)), thanks to [Corey Baker](https://github.com/cbaker6). ### 1.2.1 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.2.0...1.2.1) @@ -679,7 +689,7 @@ __Improvements__ __Fixes__ - Delete current installation during logout ([#52](https://github.com/parse-community/Parse-Swift/pull/52)), thanks to [Corey Baker](https://github.com/cbaker6). -- Parse server supports `$eq`, but this isn't supported by LiveQueryServer, switched to supported ([#49](https://github.com/parse-community/Parse-Swift/pull/49)), thanks to [Corey Baker](https://github.com/cbaker6). +- Parse server supports `$eq`, but this is not supported by LiveQueryServer, switched to supported ([#49](https://github.com/parse-community/Parse-Swift/pull/49)), thanks to [Corey Baker](https://github.com/cbaker6). - Bug when updating a ParseObject bug where objects was accidently converted to pointers ([#48](https://github.com/parse-community/Parse-Swift/pull/48)), thanks to [Corey Baker](https://github.com/cbaker6). - User logout was calling the wrong endpoint ([#43](https://github.com/parse-community/Parse-Swift/pull/43)), thanks to [Corey Baker](https://github.com/cbaker6) and [Tom Fox](https://github.com/TomWFox). - Fix an issue where ACL was overwritten with nil ([#40](https://github.com/parse-community/Parse-Swift/pull/40)), thanks to [Corey Baker](https://github.com/cbaker6). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7ed4d130..b9c8016f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ Together we will plan out the best conceptual approach for your contribution, so When you are ready to code, you can find more information about opening a pull request in the [GitHub docs](https://help.github.com/articles/creating-a-pull-request/). -Whether this is your first contribution or you are already an experienced contributor, the Parse Community has your back – don't hesitate to ask for help! +Whether this is your first contribution or you are already an experienced contributor, the Parse Community has your back – do not hesitate to ask for help! ## Why Contributing? diff --git a/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift index 58ff02c62..b6f8cdc0d 100644 --- a/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift @@ -121,7 +121,7 @@ score.save { result in assert(savedScore.points == 10) /*: To modify, need to make it a var as the value type - was initialized as immutable. Using `mutable` + was initialized as immutable. Using `mergeable` allows you to only send the updated keys to the parse server as opposed to the whole object. */ @@ -195,7 +195,7 @@ var score2ForFetchedLater: GameScore? } }*/ -//: Save synchronously (not preferred - all operations on main queue). +//: Save synchronously (not preferred - all operations on current queue). let savedScore: GameScore? do { savedScore = try score.save() @@ -211,7 +211,7 @@ assert(savedScore?.updatedAt != nil) assert(savedScore?.points == 10) /*: To modify, need to make it a var as the value type - was initialized as immutable. Using `mutable` + was initialized as immutable. Using `mergeable` allows you to only send the updated keys to the parse server as opposed to the whole object. */ @@ -274,7 +274,7 @@ do { //: Now we will fetch a ParseObject that has already been saved based on its' objectId. let scoreToFetch = GameScore(objectId: savedScore?.objectId) -//: Asynchronously (preferred way) fetch this GameScore based on it's objectId alone. +//: Asynchronously (preferred way) fetch this GameScore based on it is objectId alone. scoreToFetch.fetch { result in switch result { case .success(let fetchedScore): @@ -284,7 +284,7 @@ scoreToFetch.fetch { result in } } -//: Synchronously fetch this GameScore based on it's objectId alone. +//: Synchronously fetch this GameScore based on it is objectId alone. do { let fetchedScore = try scoreToFetch.fetch() print("Successfully fetched: \(fetchedScore)") @@ -295,7 +295,7 @@ do { //: Now we will fetch `ParseObject`'s in batch that have already been saved based on its' objectId. let score2ToFetch = GameScore(objectId: score2ForFetchedLater?.objectId) -//: Asynchronously (preferred way) fetch GameScores based on it's objectId alone. +//: Asynchronously (preferred way) fetch GameScores based on it is objectId alone. [scoreToFetch, score2ToFetch].fetchAll { result in switch result { case .success(let fetchedScores): @@ -315,7 +315,7 @@ let score2ToFetch = GameScore(objectId: score2ForFetchedLater?.objectId) var fetchedScore: GameScore! -//: Synchronously fetchAll GameScore's based on it's objectId's alone. +//: Synchronously fetchAll GameScore's based on it is objectId's alone. do { let fetchedScores = try [scoreToFetch, score2ToFetch].fetchAll() fetchedScores.forEach { result in @@ -331,7 +331,7 @@ do { assertionFailure("Error fetching: \(error)") } -//: Asynchronously (preferred way) deleteAll GameScores based on it's objectId alone. +//: Asynchronously (preferred way) deleteAll GameScores based on it is objectId alone. [scoreToFetch, score2ToFetch].deleteAll { result in switch result { case .success(let deletedScores): @@ -348,7 +348,7 @@ do { } } -//: Synchronously deleteAll GameScore's based on it's objectId's alone. +//: Synchronously deleteAll GameScore's based on it is objectId's alone. //: Commented out because the async above deletes the items already. /* do { let fetchedScores = try [scoreToFetch, score2ToFetch].deleteAll() diff --git a/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift index 6402b7d0f..4418652a1 100644 --- a/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift @@ -109,10 +109,10 @@ var savedRole: Role? //: Now we will create the Role. guard let currentUser = User.current else { - fatalError("User currently isn't signed in") + fatalError("User currently is not signed in") } -//: Every Role requires an ACL that can't be changed after saving. +//: Every Role requires an ACL that cannot be changed after saving. var acl = ParseACL() acl.setReadAccess(user: currentUser, value: true) acl.setWriteAccess(user: currentUser, value: true) @@ -312,7 +312,7 @@ let score2 = GameScore(points: 57) print(error) } case .failure(let error): - print("Couldn't save scores. \(error)") + print("Could not save scores. \(error)") } } diff --git a/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift index e79bb5423..37bd3b448 100644 --- a/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift @@ -55,7 +55,7 @@ config.save { result in } } -//: Fetch the updated config to make sure it's saved. +//: Fetch the updated config to make sure it is saved. config.fetch { result in switch result { case .success(let currentConfig): @@ -65,7 +65,7 @@ config.fetch { result in } } -//: Anytime you fetch or update your Config successfully, it's automatically saved to your Keychain. +//: Anytime you fetch or update your Config successfully, it is automatically saved to your Keychain. print(Config.current ?? "No config") PlaygroundPage.current.finishExecution() diff --git a/ParseSwift.playground/Pages/15 - Custom ObjectId.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/15 - Custom ObjectId.xcplaygroundpage/Contents.swift index 6955bf321..1cf838030 100644 --- a/ParseSwift.playground/Pages/15 - Custom ObjectId.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/15 - Custom ObjectId.xcplaygroundpage/Contents.swift @@ -75,7 +75,7 @@ score.save { result in assert(savedScore.updatedAt != nil) assert(savedScore.points == 10) - //: Now that this object has a `createdAt`, it's properly saved to the server. + //: Now that this object has a `createdAt`, it is properly saved to the server. //: Any changes to `createdAt` and `objectId` will not be saved to the server. print("Saved score: \(savedScore)") @@ -123,10 +123,10 @@ query.first { result in } } -//: Now we will attempt to fetch a ParseObject that isn't saved. +//: Now we will attempt to fetch a ParseObject that is not saved. let scoreToFetch = GameScore(objectId: "hello") -//: Asynchronously (preferred way) fetch this GameScore based on it's objectId alone. +//: Asynchronously (preferred way) fetch this GameScore based on it is objectId alone. scoreToFetch.fetch { result in switch result { case .success(let fetchedScore): diff --git a/ParseSwift.playground/Pages/17 - SwiftUI - Finding Objects.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/17 - SwiftUI - Finding Objects.xcplaygroundpage/Contents.swift index 99fb365ba..ce353c461 100644 --- a/ParseSwift.playground/Pages/17 - SwiftUI - Finding Objects.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/17 - SwiftUI - Finding Objects.xcplaygroundpage/Contents.swift @@ -4,7 +4,7 @@ //: For this page, make sure your build target is set to ParseSwift (iOS) and targeting //: an iPhone, iPod, or iPad. Also be sure your `Playground Settings` //: in the `File Inspector` is `Platform = iOS`. This is because -//: SwiftUI in macOS Playgrounds doesn't seem to build correctly +//: SwiftUI in macOS Playgrounds does not seem to build correctly //: Be sure to switch your target and `Playground Settings` back to //: macOS after leaving this page. diff --git a/ParseSwift.playground/Pages/18 - SwiftUI - Finding Objects With Custom ViewModel.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/18 - SwiftUI - Finding Objects With Custom ViewModel.xcplaygroundpage/Contents.swift index ef9f120dc..32157e197 100644 --- a/ParseSwift.playground/Pages/18 - SwiftUI - Finding Objects With Custom ViewModel.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/18 - SwiftUI - Finding Objects With Custom ViewModel.xcplaygroundpage/Contents.swift @@ -4,7 +4,7 @@ //: For this page, make sure your build target is set to ParseSwift (iOS) and targeting //: an iPhone, iPod, or iPad. Also be sure your `Playground Settings` //: in the `File Inspector` is `Platform = iOS`. This is because -//: SwiftUI in macOS Playgrounds doesn't seem to build correctly +//: SwiftUI in macOS Playgrounds does not seem to build correctly //: Be sure to switch your target and `Playground Settings` back to //: macOS after leaving this page. diff --git a/ParseSwift.playground/Pages/19 - SwiftUI - LiveQuery.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/19 - SwiftUI - LiveQuery.xcplaygroundpage/Contents.swift index 8eca2ba8f..c1e572c1a 100644 --- a/ParseSwift.playground/Pages/19 - SwiftUI - LiveQuery.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/19 - SwiftUI - LiveQuery.xcplaygroundpage/Contents.swift @@ -4,7 +4,7 @@ //: For this page, make sure your build target is set to ParseSwift (iOS) and targeting //: an iPhone, iPod, or iPad. Also be sure your `Playground Settings` //: in the `File Inspector` is `Platform = iOS`. This is because -//: SwiftUI in macOS Playgrounds doesn't seem to build correctly +//: SwiftUI in macOS Playgrounds does not seem to build correctly //: Be sure to switch your target and `Playground Settings` back to //: macOS after leaving this page. diff --git a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift index 30b65dc90..a3bca582a 100644 --- a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift @@ -89,7 +89,7 @@ query.limit(2) } } -//: Query synchronously (not preferred - all operations on main queue). +//: Query synchronously (not preferred - all operations on current queue). let results = try query.find() assert(results.count >= 1) results.forEach { score in diff --git a/ParseSwift.playground/Pages/20 - Cloud Schemas.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/20 - Cloud Schemas.xcplaygroundpage/Contents.swift index 1cd20cf3e..a588a0051 100644 --- a/ParseSwift.playground/Pages/20 - Cloud Schemas.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/20 - Cloud Schemas.xcplaygroundpage/Contents.swift @@ -127,7 +127,7 @@ do { type: .array, options: ParseFieldOptions<[User]>(required: false, defauleValue: nil)) } catch { - print("Can't add field: \(gameScoreSchema)") + print("Cannot add field: \(gameScoreSchema)") } //: Now lets create the schema on the server. @@ -136,7 +136,7 @@ gameScoreSchema.create { result in case .success(let savedSchema): print("Check GameScore2 in Dashboard. \nThe created schema: \(savedSchema)") case .failure(let error): - print("Couldn't save schema: \(error)") + print("Could not save schema: \(error)") } } @@ -157,7 +157,7 @@ gameScoreSchema.update { result in */ gameScoreSchema = updatedSchema case .failure(let error): - print("Couldn't update schema: \(error)") + print("Could not update schema: \(error)") } } @@ -174,7 +174,7 @@ gameScoreSchema.update { result in */ gameScoreSchema = updatedSchema case .failure(let error): - print("Couldn't update schema: \(error)") + print("Could not update schema: \(error)") } } @@ -184,13 +184,13 @@ gameScoreSchema.fetch { result in case .success(let fetchedGameScore): print("The fetched GameScore2 schema is: \(fetchedGameScore)") case .failure(let error): - print("Couldn't fetch schema: \(error)") + print("Could not fetch schema: \(error)") } } /*: Fields can also be deleted on a schema. Lets remove - the **data** field since it's not going being used. + the **data** field since it is not going being used. */ gameScoreSchema = gameScoreSchema.deleteField("data") @@ -204,7 +204,7 @@ gameScoreSchema.update { result in */ gameScoreSchema = updatedSchema case .failure(let error): - print("Couldn't update schema: \(error)") + print("Could not update schema: \(error)") } } @@ -228,7 +228,7 @@ gameScoreSchema.update { result in */ gameScoreSchema = updatedSchema case .failure(let error): - print("Couldn't update schema: \(error)") + print("Could not update schema: \(error)") } } @@ -242,7 +242,7 @@ gameScore.save { result in case .success(let savedGameScore): print("The saved GameScore is: \(savedGameScore)") case .failure(let error): - print("Couldn't save schema: \(error)") + print("Could not save schema: \(error)") } } @@ -252,12 +252,12 @@ gameScoreSchema.purge { result in case .success: print("All objects have been purged from this schema.") case .failure(let error): - print("Couldn't purge schema: \(error)") + print("Could not purge schema: \(error)") } } /*: - As long as there's no data in your `ParseSchema` you can + As long as there is no data in your `ParseSchema` you can delete the schema. */ gameScoreSchema.delete { result in @@ -265,7 +265,7 @@ gameScoreSchema.purge { result in case .success: print("The schema has been deleted.") case .failure(let error): - print("Couldn't delete the schema: \(error)") + print("Could not delete the schema: \(error)") } } diff --git a/ParseSwift.playground/Pages/21 - Cloud Push Notifications.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/21 - Cloud Push Notifications.xcplaygroundpage/Contents.swift index 0f18d1914..8ede40f46 100644 --- a/ParseSwift.playground/Pages/21 - Cloud Push Notifications.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/21 - Cloud Push Notifications.xcplaygroundpage/Contents.swift @@ -76,17 +76,17 @@ push.send { result in //: Update the stored property with the lastest status id. pushStatusId = statusId case .failure(let error): - print("Couldn't create push: \(error)") + print("Could not create push: \(error)") } } -//: You can fetch the status of notificaiton if you know it's id. +//: You can fetch the status of notificaiton if you know it is id. push.fetchStatus(pushStatusId) { result in switch result { case .success(let pushStatus): print("The push status is: \"\(pushStatus)\"") case .failure(let error): - print("Couldn't fetch push status: \(error)") + print("Could not fetch push status: \(error)") } } @@ -110,7 +110,7 @@ push2.send { result in //: Update the stored property with the lastest status id. pushStatusId = statusId case .failure(let error): - print("Couldn't create push: \(error)") + print("Could not create push: \(error)") } } @@ -123,7 +123,7 @@ push2.fetchStatus(pushStatusId) { result in case .success(let pushStatus): print("The push status is: \"\(pushStatus)\"") case .failure(let error): - print("Couldn't fetch push status: \(error)") + print("Could not fetch push status: \(error)") } } @@ -143,7 +143,7 @@ push3.send { result in //: Update the stored property with the lastest status id. pushStatusId = statusId case .failure(let error): - print("Couldn't create push: \(error)") + print("Could not create push: \(error)") } } @@ -156,7 +156,7 @@ push3.fetchStatus(pushStatusId) { result in case .success(let pushStatus): print("The Firebase push status is: \"\(pushStatus)\"") case .failure(let error): - print("Couldn't fetch push status: \(error)") + print("Could not fetch push status: \(error)") } } @@ -177,7 +177,7 @@ query.findAll(options: [.useMasterKey]) { result in case .success(let pushStatus): print("All matching statuses: \"\(pushStatus)\"") case .failure(let error): - print("Couldn't perform query: \(error)") + print("Could not perform query: \(error)") } } diff --git a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift index 2a0179a4d..a9834d77d 100644 --- a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift @@ -149,7 +149,7 @@ currentUser?.save { result in switch result { case .success(let updatedUser): - print("Successfully save custom fields of User to ParseServer: \(updatedUser)") + print("Successfully saved custom fields of User to ParseServer: \(updatedUser)") case .failure(let error): print("Failed to update user: \(error)") } diff --git a/ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift index ef69bd4c5..87670a009 100644 --- a/ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift @@ -77,7 +77,7 @@ installationToUpdate?.save { results in switch results { case .success(let updatedInstallation): - print("Successfully save myCustomInstallationKey to ParseServer: \(updatedInstallation)") + print("Successfully saved myCustomInstallationKey to ParseServer: \(updatedInstallation)") case .failure(let error): print("Failed to update installation: \(error)") } diff --git a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift index 9c04c2332..fb21a1620 100644 --- a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift @@ -105,7 +105,7 @@ do { } /*: If you only want to query for points in descending order, use the order enum. -Notice the "var", the query has to be mutable since it's a value type. +Notice the "var", the query has to be mutable since it is a value type. */ var querySorted = query querySorted.order([.descending("points")]) diff --git a/ParseSwift.playground/Pages/8 - Pointers.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/8 - Pointers.xcplaygroundpage/Contents.swift index 2443840cc..68cb47cb7 100644 --- a/ParseSwift.playground/Pages/8 - Pointers.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/8 - Pointers.xcplaygroundpage/Contents.swift @@ -123,7 +123,7 @@ author2.save { result in assert(savedAuthorAndBook.otherBooks?.count == 2) /*: - Notice the pointer objects haven't been updated on the + Notice the pointer objects have not been updated on the client.If you want the latest pointer objects, fetch and include them. */ print("Saved \(savedAuthorAndBook)") @@ -284,7 +284,7 @@ author3.otherBooks = [otherBook3, otherBook4] assert(savedAuthorAndBook.otherBooks?.count == 2) /*: - Notice the pointer objects haven't been updated on the + Notice the pointer objects have not been updated on the client.If you want the latest pointer objects, fetch and include them. */ print("Saved \(savedAuthorAndBook)") @@ -315,7 +315,7 @@ author4.otherBooks = [otherBook3, otherBook4] assert(savedAuthorAndBook.otherBooks?.count == 2) /*: - Notice the pointer objects haven't been updated on the + Notice the pointer objects have not been updated on the client.If you want the latest pointer objects, fetch and include them. */ print("Saved \(savedAuthorAndBook)") diff --git a/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift index b85eb11e8..b788dfbcf 100644 --- a/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift @@ -128,7 +128,7 @@ let helloFile = ParseFile(name: "hello.txt", data: sampleData) var score2 = GameScore(points: 105) score2.myData = helloFile -//: Save synchronously (not preferred - all operations on main queue). +//: Save synchronously (not preferred - all operations on current queue). do { let savedScore = try score2.save() print("Your hello file has been successfully saved") @@ -154,16 +154,16 @@ do { the data from the file. */ guard let dataFromParseFile = try? Data(contentsOf: fetchedFile.localURL!) else { - fatalError("Error: couldn't get data from file.") + fatalError("Error: Could not get data from file.") } //: Checking to make sure the data saved on the Parse Server is the same as the original if dataFromParseFile != sampleData { - assertionFailure("Data isn't the same. Something went wrong.") + assertionFailure("Data is not the same. Something went wrong.") } guard let parseFileString = String(data: dataFromParseFile, encoding: .utf8) else { - fatalError("Error: couldn't create String from data.") + fatalError("Error: Could not create String from data.") } print("The data saved on parse is: \"\(parseFileString)\"") } else { diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 072782501..7e7432cf9 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -632,6 +632,12 @@ 70D1BE7425BB43EB00A42E7C /* BaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */; }; 70D1BE7525BB43EB00A42E7C /* BaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */; }; 70D1BE7625BB43EB00A42E7C /* BaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */; }; + 70D41D6728B0235100613510 /* MigrateObjCSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */; }; + 70D41D6828B0235100613510 /* MigrateObjCSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */; }; + 70D41D6928B0235100613510 /* MigrateObjCSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */; }; + 70D41D6B28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */; }; + 70D41D6C28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */; }; + 70D41D6D28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */; }; 70DFEA8A2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */; }; 70DFEA8B2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */; }; 70DFEA8C2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */; }; @@ -1303,6 +1309,8 @@ 70D1BDB925BB17A600A42E7C /* ParseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfig.swift; sourceTree = ""; }; 70D1BE0625BB2BF400A42E7C /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = ""; }; 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseConfig.swift; sourceTree = ""; }; + 70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateObjCSDKTests.swift; sourceTree = ""; }; + 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateObjCSDKCombineTests.swift; sourceTree = ""; }; 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializeSDKTests.swift; sourceTree = ""; }; 70E09E1B262F0634002DD451 /* ParsePointerCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePointerCombineTests.swift; sourceTree = ""; }; 70E6B015286120E00043EC4A /* ParseHookFunctionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseHookFunctionTests.swift; sourceTree = ""; }; @@ -1556,6 +1564,8 @@ 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */, 709B40C0268F999000ED2EAC /* IOS13Tests.swift */, 4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */, + 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */, + 70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */, 9194657724F16E330070296B /* ParseACLTests.swift */, 917BA4292703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift */, 91CB9536265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift */, @@ -1619,8 +1629,8 @@ 7003963A25A288100052CB31 /* ParseLiveQueryTests.swift */, 703B091A26BDE774005A112F /* ParseObjectAsyncTests.swift */, 70C7DC2024D20F190050419B /* ParseObjectBatchTests.swift */, - 70732C592606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift */, 7044C1DE25C5C70D0011F6E7 /* ParseObjectCombineTests.swift */, + 70732C592606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift */, 911DB13524C4FC100027F3C7 /* ParseObjectTests.swift */, 917BA43D2703E84000F8D747 /* ParseOperationAsyncTests.swift */, 7044C1EB25C5CC930011F6E7 /* ParseOperationCombineTests.swift */, @@ -2403,6 +2413,7 @@ 4AB8B4EB1F254AE10070F682 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1210; LastUpgradeCheck = 1210; ORGANIZATIONNAME = "Parse Community"; @@ -2886,8 +2897,10 @@ 7037DAB226384DE1005D7E62 /* TestParseEncoder.swift in Sources */, 7004C24D25B69207005E0AD9 /* ParseRoleTests.swift in Sources */, 917BA42E2703E20E00F8D747 /* ParseCloudAsyncTests.swift in Sources */, + 70D41D6728B0235100613510 /* MigrateObjCSDKTests.swift in Sources */, 91678706259BC5D400BB5B4E /* ParseCloudTests.swift in Sources */, 70386A5C25D9A4020048EC1B /* ParseLDAPCombineTests.swift in Sources */, + 70D41D6B28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */, 70D1BD8725B8C37200A42E7C /* ParseRelationTests.swift in Sources */, 7003963B25A288100052CB31 /* ParseLiveQueryTests.swift in Sources */, 70E6B032286152550043EC4A /* ParseHookTriggerRequestCombineTests.swift in Sources */, @@ -3199,8 +3212,10 @@ 7037DAB426384DE1005D7E62 /* TestParseEncoder.swift in Sources */, 7004C26125B6920B005E0AD9 /* ParseRoleTests.swift in Sources */, 917BA4302703E20E00F8D747 /* ParseCloudAsyncTests.swift in Sources */, + 70D41D6928B0235100613510 /* MigrateObjCSDKTests.swift in Sources */, 9167871A259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */, 70386A5E25D9A4020048EC1B /* ParseLDAPCombineTests.swift in Sources */, + 70D41D6D28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */, 70D1BD8925B8C37200A42E7C /* ParseRelationTests.swift in Sources */, 7003963D25A288100052CB31 /* ParseLiveQueryTests.swift in Sources */, 70E6B034286152550043EC4A /* ParseHookTriggerRequestCombineTests.swift in Sources */, @@ -3319,8 +3334,10 @@ 7037DAB326384DE1005D7E62 /* TestParseEncoder.swift in Sources */, 7004C25725B6920A005E0AD9 /* ParseRoleTests.swift in Sources */, 917BA42F2703E20E00F8D747 /* ParseCloudAsyncTests.swift in Sources */, + 70D41D6828B0235100613510 /* MigrateObjCSDKTests.swift in Sources */, 91678710259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */, 70386A5D25D9A4020048EC1B /* ParseLDAPCombineTests.swift in Sources */, + 70D41D6C28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */, 70D1BD8825B8C37200A42E7C /* ParseRelationTests.swift in Sources */, 7003963C25A288100052CB31 /* ParseLiveQueryTests.swift in Sources */, 70E6B033286152550043EC4A /* ParseHookTriggerRequestCombineTests.swift in Sources */, diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index 54f1ca134..8aae02450 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -99,7 +99,7 @@ internal extension API { guard let response = responseResult else { throw ParseError(code: .unknownError, - message: "couldn't unrwrap server response") + message: "Could not unrwrap server response") } return try response.get() } @@ -231,7 +231,7 @@ internal extension API { } } } else if let otherURL = self.otherURL { - //Non-parse servers don't receive any parse dedicated request info + //Non-parse servers do not receive any parse dedicated request info var request = URLRequest(url: otherURL) request.cachePolicy = requestCachePolicy(options: options) URLSession.parse.downloadTask(with: request, mapper: mapper) { result in @@ -249,7 +249,7 @@ internal extension API { callbackQueue.async { completion(.failure(ParseError(code: .unknownError, // swiftlint:disable:next line_length - message: "Can't download the file without specifying the url"))) + message: "Cannot download the file without specifying the url"))) } } } @@ -270,13 +270,13 @@ internal extension API { guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return .failure(ParseError(code: .unknownError, - message: "couldn't unrwrap url components for \(url)")) + message: "Could not unrwrap url components for \(url)")) } components.queryItems = params guard let urlComponents = components.url else { return .failure(ParseError(code: .unknownError, - message: "couldn't create url from components for \(components)")) + message: "Could not create url from components for \(components)")) } var urlRequest = URLRequest(url: urlComponents) @@ -285,7 +285,7 @@ internal extension API { if (urlBody as? ParseCloudTypeable) != nil { guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody, skipKeys: .cloud) else { return .failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)")) + message: "Could not encode body \(urlBody)")) } urlRequest.httpBody = bodyData } else { @@ -295,7 +295,7 @@ internal extension API { objectsSavedBeforeThisOne: childObjects, filesSavedBeforeThisOne: childFiles) else { return .failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)")) + message: "Could not encode body \(urlBody)")) } urlRequest.httpBody = bodyData.encoded } @@ -352,14 +352,14 @@ internal extension API.Command { let tempFileLocation = try ParseCoding.jsonDecoder().decode(URL.self, from: data) guard let fileManager = ParseFileManager(), let defaultDirectoryPath = fileManager.defaultDataDirectoryPath else { - throw ParseError(code: .unknownError, message: "Can't create fileManager") + throw ParseError(code: .unknownError, message: "Cannot create fileManager") } let downloadDirectoryPath = defaultDirectoryPath .appendingPathComponent(ParseConstants.fileDownloadsDirectory, isDirectory: true) try fileManager.createDirectoryIfNeeded(downloadDirectoryPath.relativePath) let fileLocation = downloadDirectoryPath.appendingPathComponent(object.name) if tempFileLocation != fileLocation { - try? FileManager.default.removeItem(at: fileLocation) //Remove file if it's already present + try? FileManager.default.removeItem(at: fileLocation) //Remove file if it is already present try FileManager.default.moveItem(at: tempFileLocation, to: fileLocation) } var object = object diff --git a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift index 2a44427fa..8fe7a3d17 100644 --- a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift +++ b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift @@ -51,7 +51,7 @@ internal extension API { guard let response = responseResult else { throw ParseError(code: .unknownError, - message: "couldn't unrwrap server response") + message: "Could not unrwrap server response") } return try response.get() } @@ -94,13 +94,13 @@ internal extension API { guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return .failure(ParseError(code: .unknownError, - message: "couldn't unrwrap url components for \(url)")) + message: "Could not unrwrap url components for \(url)")) } components.queryItems = params guard let urlComponents = components.url else { return .failure(ParseError(code: .unknownError, - message: "couldn't create url from components for \(components)")) + message: "Could not create url from components for \(components)")) } var urlRequest = URLRequest(url: urlComponents) @@ -108,7 +108,7 @@ internal extension API { if let urlBody = body { guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { return .failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)")) + message: "Could not encode body \(urlBody)")) } urlRequest.httpBody = bodyData } @@ -204,10 +204,10 @@ internal extension API.NonParseBodyCommand { } } let batchCommand = BatchChildCommand(requests: batchCommands, - transaction: transaction) + transaction: transaction) return RESTBatchCommandTypeEncodablePointer(method: .POST, - path: .batch, - body: batchCommand, - mapper: mapper) + path: .batch, + body: batchCommand, + mapper: mapper) } } diff --git a/Sources/ParseSwift/API/Responses.swift b/Sources/ParseSwift/API/Responses.swift index aa80aadeb..12afa2687 100644 --- a/Sources/ParseSwift/API/Responses.swift +++ b/Sources/ParseSwift/API/Responses.swift @@ -104,7 +104,7 @@ internal struct BatchResponse: Codable { case .PATCH: return try asUpdateResponse().apply(to: object) case .GET: - fatalError("Parse-server doesn't support batch fetching like this. Try \"fetchAll\".") + fatalError("Parse-server does not support batch fetching like this. Try \"fetchAll\".") default: fatalError("There is no configured way to apply for method: \(method)") } diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift index 1e678269a..f79e1d161 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift @@ -26,12 +26,12 @@ public struct ParseApple: ParseAuthentication { /// - parameter user: Required id for the user. /// - parameter identityToken: Required identity token for the user. /// - returns: authData dictionary. - /// - throws: `ParseError` if the **identityToken** can't be converted + /// - throws: `ParseError` if the **identityToken** cannot be converted /// to a string. func makeDictionary(user: String, identityToken: Data) throws -> [String: String] { guard let identityTokenString = String(data: identityToken, encoding: .utf8) else { - throw ParseError(code: .unknownError, message: "Couldn't convert identityToken to String") + throw ParseError(code: .unknownError, message: "Could not convert identityToken to String") } return [AuthenticationKeys.id.rawValue: user, AuthenticationKeys.token.rawValue: identityTokenString] @@ -75,7 +75,7 @@ public extension ParseApple { guard let appleAuthData = try? AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken) else { callbackQueue.async { completion(.failure(.init(code: .unknownError, - message: "Couldn't create authData."))) + message: "Could not create authData."))) } return } @@ -123,7 +123,7 @@ public extension ParseApple { guard let appleAuthData = try? AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken) else { callbackQueue.async { completion(.failure(.init(code: .unknownError, - message: "Couldn't create authData."))) + message: "Could not create authData."))) } return } diff --git a/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift b/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift index 293481668..cf46c517b 100644 --- a/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift +++ b/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift @@ -12,7 +12,7 @@ import Foundation Provides utility functions for working with Anonymously logged-in users. Anonymous users have some unique characteristics: - - Anonymous users don't need a user name or password. + - Anonymous users do not need a user name or password. - Once logged out, an anonymous user cannot be recovered. - When the current user is anonymous, the following methods can be used to switch to a different user or convert the anonymous user into a regular one: diff --git a/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift index 385462581..fff2c65bb 100644 --- a/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift +++ b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift @@ -442,7 +442,7 @@ public extension ParseUser { if let current = Self.current { guard current.hasSameObjectId(as: mutableSelf) else { let error = ParseError(code: .unknownError, - message: "Can't signup a user with a different objectId than the current user") + message: "Cannot signup a user with a different objectId than the current user") throw error } } diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index d59c59863..f241537b9 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -34,7 +34,7 @@ private protocol _JSONStringDictionaryEncodableMarker { } #endif extension Dictionary: _JSONStringDictionaryEncodableMarker where Key == String, Value: Encodable { } -// This rule doesn't allow types with underscores in their names. +// This rule does not allow types with underscores in their names. // swiftlint:disable type_name // swiftlint:disable colon // swiftlint:disable force_cast @@ -237,18 +237,18 @@ internal class _ParseEncoder: JSONEncoder, Encoder { /// /// **true** if an element has not yet been encoded at this coding path; **false** otherwise. var canEncodeNewValue: Bool { - // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). + // Every time a new value gets encoded, the key it is encoded for is pushed onto the coding path (even if it is a nil key from an unkeyed container). // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. // If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition. // // This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path. - // Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here). + // Things which will not request containers do not need to have the coding path extended for them (but it does not matter if it is, because they will not reach here). return self.storage.count == self.codingPath.count } @available(*, unavailable) override func encode(_ value: T) throws -> Data { - throw ParseError(code: .unknownError, message: "This method shouldn't be used. Either use the JSONEncoder or if you are encoding a ParseObject use \"encodeObject\"") + throw ParseError(code: .unknownError, message: "This method should not be used. Either use the JSONEncoder or if you are encoding a ParseObject use \"encodeObject\"") } func encodeObject(_ value: Encodable, @@ -289,7 +289,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder { // If an existing keyed container was already requested, return that one. let topContainer: NSMutableDictionary if self.canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. + // We have not yet pushed a container at this level; do so here. topContainer = self.storage.pushKeyedContainer() } else { guard let container = self.storage.containers.last as? NSMutableDictionary else { @@ -315,7 +315,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder { // If an existing unkeyed container was already requested, return that one. let topContainer: NSMutableArray if self.canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. + // We have not yet pushed a container at this level; do so here. topContainer = self.storage.pushUnkeyedContainer() } else { guard let container = self.storage.containers.last as? NSMutableArray else { @@ -364,7 +364,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder { self.newObjects.append(object) } else if dictionary.count > 0 { // Only top level objects can be saved without a pointer - throw ParseError(code: .unknownError, message: "Error. Couldn't resolve unsaved object while encoding.") + throw ParseError(code: .unknownError, message: "Error. Could not resolve unsaved object while encoding.") } } } @@ -393,7 +393,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder { valueToEncode = currentFile } else if dictionary.count > 0 { //Only top level objects can be saved without a pointer - throw ParseError(code: .unknownError, message: "Error. Couldn't resolve unsaved file while encoding.") + throw ParseError(code: .unknownError, message: "Error. Could not resolve unsaved file while encoding.") } } return valueToEncode @@ -807,7 +807,7 @@ extension _ParseEncoder { switch self.options.dateEncodingStrategy { case .deferredToDate: // Must be called with a surrounding with(pushedKey:) call. - // Dates encode as single-value objects; this can't both throw and push a container, so no need to catch the error. + // Dates encode as single-value objects; this cannot both throw and push a container, so no need to catch the error. try date.encode(to: self) return self.storage.popContainer() @@ -841,7 +841,7 @@ extension _ParseEncoder { } guard self.storage.count > depth else { - // The closure didn't encode anything. Return the default keyed container. + // The closure did not encode anything. Return the default keyed container. return NSDictionary() } @@ -861,7 +861,7 @@ extension _ParseEncoder { try data.encode(to: self) } catch { // If the value pushed a container before throwing, pop it back off to restore state. - // This shouldn't be possible for Data (which encodes as an array of bytes), but it can't hurt to catch a failure. + // This should not be possible for Data (which encodes as an array of bytes), but it cannot hurt to catch a failure. if self.storage.count > depth { let _ = self.storage.popContainer() } @@ -888,7 +888,7 @@ extension _ParseEncoder { } guard self.storage.count > depth else { - // The closure didn't encode anything. Return the default keyed container. + // The closure did not encode anything. Return the default keyed container. return NSDictionary() } @@ -984,7 +984,7 @@ extension _ParseEncoder { // MARK: - _ParseReferencingEncoder // swiftlint:disable line_length /// __JSONReferencingEncoder is a special subclass of __JSONEncoder which has its own storage, but references the contents of a different encoder. -/// It's used in superEncoder(), which returns a new encoder for encoding a superclass -- the lifetime of the encoder should not escape the scope it's created in, but it doesn't necessarily know when it's done being used (to write to the original container). +/// It's used in superEncoder(), which returns a new encoder for encoding a superclass -- the lifetime of the encoder should not escape the scope it is created in, but it does not necessarily know when it is done being used (to write to the original container). private class _ParseReferencingEncoder: _ParseEncoder { // MARK: Reference types. diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index 0b3539137..0e4e882f7 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -217,7 +217,7 @@ internal extension URLSession { mapper: mapper)) } } else { - completion(.failure(ParseError(code: .unknownError, message: "data and file both can't be nil"))) + completion(.failure(ParseError(code: .unknownError, message: "data and file both cannot be nil"))) } if let task = task { ParseSwift.sessionDelegate.uploadDelegates[task] = progress diff --git a/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift b/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift index 95f640d79..92c99b04f 100644 --- a/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift +++ b/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift @@ -58,7 +58,7 @@ extension LiveQuerySocket { additionalProperties: true)) guard let encodedAsString = String(data: encoded, encoding: .utf8) else { let error = ParseError(code: .unknownError, - message: "Couldn't encode connect message: \(encoded)") + message: "Could not encode connect message: \(encoded)") completion(error) return } @@ -101,7 +101,7 @@ extension LiveQuerySocket { self.delegates[task]?.received(data) } else { let parseError = ParseError(code: .unknownError, - message: "Couldn't encode LiveQuery string as data") + message: "Could not encode LiveQuery string as data") self.delegates[task]?.receivedError(parseError) } self.receive(task) diff --git a/Sources/ParseSwift/LiveQuery/Operations.swift b/Sources/ParseSwift/LiveQuery/Operations.swift index b23955461..f0ee31b7e 100644 --- a/Sources/ParseSwift/LiveQuery/Operations.swift +++ b/Sources/ParseSwift/LiveQuery/Operations.swift @@ -24,7 +24,7 @@ enum OperationErrorResponse: String, Codable { case error } -// An opaque placeholder structed used to ensure that we type-safely create request IDs and don't shoot ourself in +// An opaque placeholder structed used to ensure that we type-safely create request IDs and do not shoot ourself in // the foot with array indexes. struct RequestId: Hashable, Equatable, Codable { let value: Int diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift index 806bb5c4c..459aff9f7 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift @@ -44,7 +44,7 @@ import FoundationNetworking the default. 3. You want to change the default url for all LiveQuery connections when the app is already running. Initializing new instances will create a new task/connection to the `ParseLiveQuery` server. - When an instance is deinitialized it will automatically close it's connection gracefully. + When an instance is deinitialized it will automatically close it is connection gracefully. */ public final class ParseLiveQuery: NSObject { // Queues @@ -202,7 +202,7 @@ Not attempting to open ParseLiveQuery socket anymore guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { let error = ParseError(code: .unknownError, - message: "ParseLiveQuery Error: couldn't create components from url: \(url!)") + message: "ParseLiveQuery Error: Could not create components from url: \(url!)") throw error } components.scheme = (components.scheme == "https" || components.scheme == "wss") ? "wss" : "ws" @@ -381,7 +381,7 @@ extension ParseLiveQuery: LiveQuerySocketDelegate { //Check if this is an error response if let error = try? ParseCoding.jsonDecoder().decode(ErrorResponse.self, from: data) { if !error.reconnect { - //Treat this as a user disconnect because the server doesn't want to hear from us anymore + //Treat this as a user disconnect because the server does not want to hear from us anymore self.close() } guard let parseError = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) else { @@ -402,13 +402,13 @@ extension ParseLiveQuery: LiveQuerySocketDelegate { //Check if this is a connected response guard let response = try? ParseCoding.jsonDecoder().decode(ConnectionResponse.self, from: data), response.op == .connected else { - //If not connected, shouldn't receive anything other than a connection response + //If not connected, should not receive anything other than a connection response guard let outOfOrderMessage = try? ParseCoding .jsonDecoder() .decode(AnyCodable.self, from: data) else { let error = ParseError(code: .unknownError, // swiftlint:disable:next line_length - message: "ParseLiveQuery Error: Received message out of order, but couldn't decode it") + message: "ParseLiveQuery Error: Received message out of order, but could not decode it") self.notificationQueue.async { self.receiveDelegate?.received(error) } @@ -803,7 +803,7 @@ extension ParseLiveQuery { message: message, handler: handler ) else { - throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: Couldn't create subscription.") + throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: Could not create subscription.") } self.send(record: subscriptionRecord, requestId: requestId) { _ in } diff --git a/Sources/ParseSwift/LiveQuery/Subscription.swift b/Sources/ParseSwift/LiveQuery/Subscription.swift index 0d0b3e4b4..7237c7a27 100644 --- a/Sources/ParseSwift/LiveQuery/Subscription.swift +++ b/Sources/ParseSwift/LiveQuery/Subscription.swift @@ -19,7 +19,7 @@ import Foundation */ open class Subscription: QueryViewModel, QuerySubscribable { - /// Updates and notifies when there's a new event related to a specific query. + /// Updates and notifies when there is a new event related to a specific query. open var event: (query: Query, event: Event)? { willSet { if newValue != nil { @@ -74,7 +74,7 @@ open class Subscription: QueryViewModel, QuerySubscribable { // Need to decode the event with respect to the `ParseObject`. let eventMessage = try ParseCoding.jsonDecoder().decode(EventResponse.self, from: eventData) guard let event = Event(event: eventMessage) else { - throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: couldn't create event.") + throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: Could not create event.") } self.event = (query, event) } diff --git a/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift b/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift index 90b609363..34d17d81c 100644 --- a/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift +++ b/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift @@ -88,7 +88,7 @@ open class SubscriptionCallback: QuerySubscribable { // Need to decode the event with respect to the `ParseObject`. let eventMessage = try ParseCoding.jsonDecoder().decode(EventResponse.self, from: eventData) guard let event = Event(event: eventMessage) else { - throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: couldn't create event.") + throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: Could not create event.") } eventHandlers.forEach { $0(query, event) } } diff --git a/Sources/ParseSwift/Objects/ParseInstallation+async.swift b/Sources/ParseSwift/Objects/ParseInstallation+async.swift index 7ec68ac39..f0670ceee 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+async.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+async.swift @@ -300,4 +300,56 @@ public extension Sequence where Element: ParseInstallation { } } +#if !os(Linux) && !os(Android) && !os(Windows) +// MARK: Migrate from Objective-C SDK +public extension ParseInstallation { + /** + Migrates the `ParseInstallation` *asynchronously* from the Objective-C SDK Keychain. + + - parameter copyEntireInstallation: When **true**, copies the + entire `ParseInstallation` from the Objective-C SDK Keychain to the Swift SDK. When + **false**, only the `channels` and `deviceToken` are copied from the Objective-C + SDK Keychain; resulting in a new `ParseInstallation` for original `sessionToken`. + Defaults to **true**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns saved `ParseInstallation`. + - throws: An error of type `ParseError`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: When initializing the Swift SDK, `migratingFromObjcSDK` should be set to **false** + when calling this method. + - warning: The latest **PFInstallation** from the Objective-C SDK should be saved to your + Parse Server before calling this method. + */ + @discardableResult static func migrateFromObjCKeychain(copyEntireInstallation: Bool = true, + deleteObjectiveCKeychain: Bool = false, + options: API.Options = []) async throws -> Self { + try await withCheckedThrowingContinuation { continuation in + Self.migrateFromObjCKeychain(copyEntireInstallation: copyEntireInstallation, + options: options, + completion: continuation.resume) + } + } + + /** + Deletes the Objective-C Keychain along with the Objective-C `ParseInstallation` + from the Parse Server *asynchronously*. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns saved `ParseInstallation`. + - throws: An error of type `ParseError`. + - warning: It is recommended to only use this method after a succesfful migration. Calling this + method will destroy the entire Objective-C Keychain and `ParseInstallation` on the Parse + Server. + */ + static func deleteObjCKeychain(options: API.Options = []) async throws { + let result = try await withCheckedThrowingContinuation { continuation in + Self.deleteObjCKeychain(options: options, completion: continuation.resume) + } + if case let .failure(error) = result { + throw error + } + } +} +#endif #endif diff --git a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift index 07020cd04..0e9d802f8 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift @@ -293,4 +293,52 @@ public extension Sequence where Element: ParseInstallation { } } +#if !os(Linux) && !os(Android) && !os(Windows) +// MARK: Migrate from Objective-C SDK +public extension ParseInstallation { + + /** + Migrates the `ParseInstallation` *asynchronously* from the Objective-C SDK Keychain + and publishes when complete. + + - parameter copyEntireInstallation: When **true**, copies the + entire `ParseInstallation` from the Objective-C SDK Keychain to the Swift SDK. When + **false**, only the `channels` and `deviceToken` are copied from the Objective-C + SDK Keychain; resulting in a new `ParseInstallation` for original `sessionToken`. + Defaults to **true**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: When initializing the Swift SDK, `migratingFromObjcSDK` should be set to **false** + when calling this method. + - warning: The latest **PFInstallation** from the Objective-C SDK should be saved to your + Parse Server before calling this method. + */ + static func migrateFromObjCKeychainPublisher(copyEntireInstallation: Bool = true, + options: API.Options = []) -> Future { + Future { promise in + Self.migrateFromObjCKeychain(copyEntireInstallation: copyEntireInstallation, + options: options, + completion: promise) + } + } + + /** + Deletes the Objective-C Keychain along with the Objective-C `ParseInstallation` + from the Parse Server *asynchronously* and publishes when complete. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - warning: It is recommended to only use this method after a succesfful migration. Calling this + method will destroy the entire Objective-C Keychain and `ParseInstallation` on the Parse + Server. + */ + static func deleteObjCKeychainPublisher(options: API.Options = []) -> Future { + Future { promise in + Self.deleteObjCKeychain(options: options, completion: promise) + } + } +} +#endif #endif diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 49261cabd..9d0df958a 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -30,7 +30,7 @@ import Foundation stored in `ParseInstallation.badge` before saving/updating the installation. - warning: Linux developers should set `appName`, `appIdentifier`, and `appVersion` - manually as `ParseSwift` doesn't have access to Bundle.main. + manually as `ParseSwift` does not have access to Bundle.main. */ public protocol ParseInstallation: ParseObject { @@ -99,7 +99,7 @@ public extension ParseInstallation { func mergeParse(with object: Self) throws -> Self { guard hasSameObjectId(as: object) == true else { throw ParseError(code: .unknownError, - message: "objectId's of objects don't match") + message: "objectId's of objects do not match") } var updatedInstallation = self if shouldRestoreKey(\.ACL, @@ -221,7 +221,7 @@ public extension ParseInstallation { guard let installationFromKeyChain: CurrentInstallationContainer = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { - // Couldn't create container correctly, return empty one. + // Could not create container correctly, return empty one. return CurrentInstallationContainer() } try? ParseStorage.shared.set(installationFromKeyChain, for: ParseStorage.Keys.currentInstallation) @@ -242,7 +242,7 @@ public extension ParseInstallation { guard let installationFromMemory: CurrentInstallationContainer = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { - // Couldn't create container correctly, return empty one. + // Could not create container correctly, return empty one. return CurrentInstallationContainer() } return installationFromMemory @@ -282,7 +282,7 @@ public extension ParseInstallation { #if !os(Linux) && !os(Android) && !os(Windows) try? KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) #endif - //Prepare new installation + // Prepare new installation BaseParseInstallation.createNewInstallationIfNeeded() } @@ -339,7 +339,7 @@ extension ParseInstallation { // If using an Xcode new enough to know about Mac Catalyst: // Mac Catalyst Apps use a prefix to the bundle ID. This should not be transmitted // to Parse Server. Catalyst apps should look like iOS apps otherwise - // push and other services don't work properly. + // push and other services do not work properly. if let currentAppIdentifier = appInfo[String(kCFBundleIdentifierKey)] as? String { let macCatalystBundleIdPrefix = "maccatalyst." if currentAppIdentifier.hasPrefix(macCatalystBundleIdPrefix) { @@ -381,7 +381,7 @@ extension ParseInstallation { The country codes are two-letter uppercase ISO country codes (such as "US") as defined by ISO 3166-1. - Many iOS locale identifiers don't contain the country code -> inconsistencies with Android/Windows Phone. + Many iOS locale identifiers do not contain the country code -> inconsistencies with Android/Windows Phone. */ mutating func updateLocaleIdentifierFromDevice() { guard let language = Locale.current.languageCode else { @@ -526,7 +526,7 @@ extension ParseInstallation { extension ParseInstallation { /** - Saves the `ParseInstallation` *synchronously* and throws an error if there's an issue. + Saves the `ParseInstallation` *synchronously* and throws an error if there is an issue. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. @@ -539,7 +539,7 @@ extension ParseInstallation { } /** - Saves the `ParseInstallation` *synchronously* and throws an error if there's an issue. + Saves the `ParseInstallation` *synchronously* and throws an error if there is an issue. - parameter ignoringCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.isAllowingCustomObjectIds = true` to allow for mixed @@ -1296,7 +1296,7 @@ public extension Sequence where Element: ParseInstallation { - returns: Returns a Result enum with the object if a fetch was successful or a `ParseError` if it failed. - throws: An error of type `ParseError`. - important: If an object fetched has the same objectId as current, it will automatically update the current. - - warning: The order in which installations are returned are not guarenteed. You shouldn't expect results in + - warning: The order in which installations are returned are not guarenteed. You should not expect results in any particular order. */ func fetchAll(includeKeys: [String]? = nil, @@ -1341,7 +1341,7 @@ public extension Sequence where Element: ParseInstallation { - parameter completion: The block to execute. It should have the following argument signature: `(Result<[(Result)], ParseError>)`. - important: If an object fetched has the same objectId as current, it will automatically update the current. - - warning: The order in which installations are returned are not guarenteed. You shouldn't expect results in + - warning: The order in which installations are returned are not guarenteed. You should not expect results in any particular order. */ func fetchAll( @@ -1509,4 +1509,149 @@ public extension Sequence where Element: ParseInstallation { } } } -} // swiftlint:disable:this file_length +} + +#if !os(Linux) && !os(Android) && !os(Windows) +// MARK: Migrate from Objective-C SDK +public extension ParseInstallation { + + /** + Migrates the `ParseInstallation` *asynchronously* from the Objective-C SDK Keychain. + + - parameter copyEntireInstallation: When **true**, copies the + entire `ParseInstallation` from the Objective-C SDK Keychain to the Swift SDK. When + **false**, only the `channels` and `deviceToken` are copied from the Objective-C + SDK Keychain; resulting in a new `ParseInstallation` for original `sessionToken`. + Defaults to **true**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default value of .main. + - parameter completion: The block to execute. + It should have the following argument signature: `(Result)`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: When initializing the Swift SDK, `migratingFromObjcSDK` should be set to **false** + when calling this method. + - warning: The latest **PFInstallation** from the Objective-C SDK should be saved to your + Parse Server before calling this method. + */ + static func migrateFromObjCKeychain(copyEntireInstallation: Bool = true, + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + guard let objcParseKeychain = KeychainStore.objectiveC, + let oldInstallationId: String = objcParseKeychain.object(forKey: "installationId") else { + let error = ParseError(code: .unknownError, + message: "Could not find Installation in the Objective-C SDK Keychain") + callbackQueue.async { + completion(.failure(error)) + } + return + } + guard var currentInstallation = Self.current else { + let error = ParseError(code: .unknownError, + message: "Current installation does not exist") + callbackQueue.async { + completion(.failure(error)) + } + return + } + guard currentInstallation.installationId != oldInstallationId else { + // If the installationId's are the same, assume successful migration already occured. + callbackQueue.async { + completion(.success(currentInstallation)) + } + return + } + currentInstallation.installationId = oldInstallationId + currentInstallation.fetch(options: options, callbackQueue: callbackQueue) { result in + switch result { + case .success(var updatedInstallation): + if copyEntireInstallation { + updatedInstallation.updateAutomaticInfo() + Self.currentContainer.installationId = updatedInstallation.installationId + Self.currentContainer.currentInstallation = updatedInstallation + } else { + Self.current?.channels = updatedInstallation.channels + if Self.current?.deviceToken == nil { + Self.current?.deviceToken = updatedInstallation.deviceToken + } + } + Self.saveCurrentContainerToKeychain() + guard let latestInstallation = Self.current else { + let error = ParseError(code: .unknownError, + message: "Had trouble migrating the installation") + callbackQueue.async { + completion(.failure(error)) + } + return + } + latestInstallation.save(options: options, + callbackQueue: callbackQueue, + completion: completion) + case .failure(let error): + callbackQueue.async { + completion(.failure(error)) + } + } + } + } + + /** + Deletes the Objective-C Keychain along with the Objective-C `ParseInstallation` + from the Parse Server *asynchronously* and executes the given callback block. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - warning: It is recommended to only use this method after a succesfful migration. Calling this + method will destroy the entire Objective-C Keychain and `ParseInstallation` on the Parse + Server. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + */ + static func deleteObjCKeychain(options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + guard let objcParseKeychain = KeychainStore.objectiveC, + let oldInstallationId: String = objcParseKeychain.object(forKey: "installationId") else { + let error = ParseError(code: .unknownError, + message: "Could not find Installation in the Objective-C SDK Keychain") + callbackQueue.async { + completion(.failure(error)) + } + return + } + guard var currentInstallation = Self.current else { + let error = ParseError(code: .unknownError, + message: "Current installation does not exist") + callbackQueue.async { + completion(.failure(error)) + } + return + } + currentInstallation.installationId = oldInstallationId + do { + try ParseSwift.deleteObjectiveCKeychain() + // Only delete the `ParseInstallation` on Parse Server if it is not current. + guard Self.current?.installationId == oldInstallationId else { + currentInstallation.delete(options: options, + callbackQueue: callbackQueue, + completion: completion) + return + } + callbackQueue.async { + completion(.success(())) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } + return + } + } +} +#endif // swiftlint:disable:this file_length diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 23c3cea8c..9f86b06a6 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -34,10 +34,10 @@ import Foundation - warning: If you plan to use "reference types" (classes), you are using at your risk as this SDK is not designed for reference types and may have unexpected behavior when it comes to threading. You will also need to implement your own `==` method to conform to `Equatable` along with with the `hash` method to conform to `Hashable`. - It is important to note that for unsaved `ParseObject`'s, you won't be able to rely on `objectId` for - `Equatable` and `Hashable` as your unsaved objects won't have this value yet and is nil. A possible way to + It is important to note that for unsaved `ParseObject`'s, you will not be able to rely on `objectId` for + `Equatable` and `Hashable` as your unsaved objects will not have this value yet and is nil. A possible way to address this is by creating a `UUID` for your objects locally and relying on that for `Equatable` and `Hashable`, - otherwise it's possible you will get "circular dependency errors" depending on your implementation. + otherwise it is possible you will get "circular dependency errors" depending on your implementation. - note: If you plan to use custom encoding/decoding, be sure to add `objectId`, `createdAt`, `updatedAt`, and `ACL` to your `ParseObject` `CodingKeys`. */ @@ -97,8 +97,8 @@ public protocol ParseObject: ParseTypeable, - throws: An error of type `ParseError`. - note: This is used in combination with `merge` to only send updated properties to the server and then merge those changes with the original object. - - warning: You should only call this method and shouldn't implement it directly - as it's already implemented for developers to use. + - warning: You should only call this method and should not implement it directly + as it is already implemented for developers to use. */ func mergeParse(with object: Self) throws -> Self @@ -194,7 +194,7 @@ public extension ParseObject { func mergeParse(with object: Self) throws -> Self { guard hasSameObjectId(as: object) == true else { throw ParseError(code: .unknownError, - message: "objectId's of objects don't match") + message: "objectId's of objects do not match") } var updated = self if shouldRestoreKey(\.ACL, @@ -219,7 +219,7 @@ public extension Sequence where Element: ParseObject { if objectCount > batchLimit { let error = ParseError(code: .unknownError, message: """ -The amount of objects (\(objectCount)) can't exceed the batch size(\(batchLimit)). +The amount of objects (\(objectCount)) cannot exceed the batch size(\(batchLimit)). Either decrease the amount of objects, increase the batch size, or disable transactions for this call. """) @@ -604,7 +604,7 @@ transactions for this call. - returns: Returns an array of Result enums with the object if a fetch was successful or a `ParseError` if it failed. - throws: An error of type `ParseError`. - - warning: The order in which objects are returned are not guarenteed. You shouldn't expect results in + - warning: The order in which objects are returned are not guarenteed. You should not expect results in any particular order. */ func fetchAll(includeKeys: [String]? = nil, @@ -645,7 +645,7 @@ transactions for this call. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: The block to execute. It should have the following argument signature: `(Result<[(Result)], ParseError>)`. - - warning: The order in which objects are returned are not guarenteed. You shouldn't expect results in + - warning: The order in which objects are returned are not guarenteed. You should not expect results in any particular order. */ func fetchAll( @@ -895,7 +895,7 @@ extension ParseObject { extension ParseObject { /** - Saves the `ParseObject` *synchronously* and throws an error if there's an issue. + Saves the `ParseObject` *synchronously* and throws an error if there is an issue. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. @@ -907,7 +907,7 @@ extension ParseObject { } /** - Saves the `ParseObject` *synchronously* and throws an error if there's an issue. + Saves the `ParseObject` *synchronously* and throws an error if there is an issue. - parameter ignoringCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.isAllowingCustomObjectIds = true` to allow for mixed `objectId` environments. Defaults to false. @@ -1167,7 +1167,7 @@ extension ParseObject { //If this ParseObject has no additional children, it can be saved now savableObjects.append(parseObject) } else { - //Else this ParseObject needs to wait until it's children are saved + //Else this ParseObject needs to wait until it is children are saved nextBatch.append(parseObject) } } diff --git a/Sources/ParseSwift/Objects/ParseUser+async.swift b/Sources/ParseSwift/Objects/ParseUser+async.swift index fbc702e9f..86a3fbea6 100644 --- a/Sources/ParseSwift/Objects/ParseUser+async.swift +++ b/Sources/ParseSwift/Objects/ParseUser+async.swift @@ -15,7 +15,7 @@ public extension ParseUser { /** Signs up the user *asynchronously*. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter username: The username of the user. @@ -40,7 +40,7 @@ public extension ParseUser { /** Signs up the user *asynchronously*. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -100,6 +100,29 @@ public extension ParseUser { } } +#if !os(Linux) && !os(Android) && !os(Windows) + /** + Logs in a `ParseUser` *asynchronously* using the session token from the Parse Objective-C SDK Keychain. + Returns an instance of the successfully logged in `ParseUser`. The Parse Objective-C SDK Keychain is not + modified in any way when calling this method; allowing developers to revert their applications back to the older + SDK if desired. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns the logged in `ParseUser`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: When initializing the Swift SDK, `migratingFromObjcSDK` should be set to **false** + when calling this method. + - warning: The latest **PFUser** from the Objective-C SDK should be saved to your + Parse Server before calling this method. + */ + @discardableResult static func loginUsingObjCKeychain(options: API.Options = []) async throws -> Self { + try await withCheckedThrowingContinuation { continuation in + Self.loginUsingObjCKeychain(options: options, completion: continuation.resume) + } + } +#endif + /** Logs out the currently logged in user *asynchronously*. diff --git a/Sources/ParseSwift/Objects/ParseUser+combine.swift b/Sources/ParseSwift/Objects/ParseUser+combine.swift index bd4be53ff..fc43a8e1c 100644 --- a/Sources/ParseSwift/Objects/ParseUser+combine.swift +++ b/Sources/ParseSwift/Objects/ParseUser+combine.swift @@ -16,7 +16,7 @@ public extension ParseUser { /** Signs up the user *asynchronously* and publishes value. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter username: The username of the user. @@ -40,7 +40,7 @@ public extension ParseUser { /** Signs up the user *asynchronously* and publishes value. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -96,6 +96,29 @@ public extension ParseUser { } } +#if !os(Linux) && !os(Android) && !os(Windows) + /** + Logs in a `ParseUser` *asynchronously* using the session token from the Parse Objective-C SDK Keychain. + Publishes an instance of the successfully logged in `ParseUser`. The Parse Objective-C SDK Keychain is not + modified in any way when calling this method; allowing developers to revert their applications back to the older + SDK if desired. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: When initializing the Swift SDK, `migratingFromObjcSDK` should be set to **false** + when calling this method. + - warning: The latest **PFUser** from the Objective-C SDK should be saved to your + Parse Server before calling this method. + */ + static func loginUsingObjCKeychainPublisher(options: API.Options = []) -> Future { + Future { promise in + Self.loginUsingObjCKeychain(options: options, completion: promise) + } + } +#endif + /** Logs out the currently logged in user *asynchronously*. Publishes when complete. diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index b88e7d6c7..e0e5e60db 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -294,7 +294,30 @@ extension ParseUser { options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - var newUser = self + Self.become(sessionToken: sessionToken, + options: options, + callbackQueue: callbackQueue, + completion: completion) + } + + /** + Logs in a `ParseUser` *asynchronously* with a session token. On success, this saves the session + to the keychain, so you can retrieve the currently logged in user using *current*. + + - parameter sessionToken: The sessionToken of the user to login. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + */ + public static func become(sessionToken: String, + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + var newUser = Self() newUser.objectId = "me" var options = options options.insert(.sessionToken(sessionToken)) @@ -320,6 +343,66 @@ extension ParseUser { } } +#if !os(Linux) && !os(Android) && !os(Windows) + /** + Logs in a `ParseUser` *asynchronously* using the session token from the Parse Objective-C SDK Keychain. + Returns an instance of the successfully logged in `ParseUser`. The Parse Objective-C SDK Keychain is not + modified in any way when calling this method; allowing developers to revert their applications back to the older + SDK if desired. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: When initializing the Swift SDK, `migratingFromObjcSDK` should be set to **false** + when calling this method. + - warning: The latest **PFUser** from the Objective-C SDK should be saved to your + Parse Server before calling this method. + */ + public static func loginUsingObjCKeychain(options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + + let objcParseKeychain = KeychainStore.objectiveC + let objcParseSessionToken: String? = objcParseKeychain?.object(forKey: "sessionToken") ?? + objcParseKeychain?.object(forKey: "session_token") + + guard let sessionToken = objcParseSessionToken else { + let error = ParseError(code: .unknownError, + message: "Could not find a session token in the Parse Objective-C SDK Keychain.") + callbackQueue.async { + completion(.failure(error)) + } + return + } + + guard let currentUser = Self.current else { + become(sessionToken: sessionToken, + options: options, + callbackQueue: callbackQueue, + completion: completion) + return + } + + guard currentUser.sessionToken == sessionToken else { + let error = ParseError(code: .unknownError, + message: """ + Currently logged in as a ParseUser who has a different + session token than the Objective-C Parse SDK session token. Please log out before + calling this method. + """) + callbackQueue.async { + completion(.failure(error)) + } + return + } + completion(.success(currentUser)) + } +#endif + internal func meCommand(sessionToken: String) throws -> API.Command { return API.Command(method: .GET, @@ -606,7 +689,7 @@ extension ParseUser { /** Signs up the user *synchronously*. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter username: The username of the user. @@ -635,7 +718,7 @@ extension ParseUser { /** Signs up the user *synchronously*. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -658,7 +741,7 @@ extension ParseUser { /** Signs up the user *asynchronously*. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -713,7 +796,7 @@ extension ParseUser { /** Signs up the user *asynchronously*. - This will also enforce that the username isn't already taken. + This will also enforce that the username is not already taken. - warning: Make sure that password and username are set before calling this method. - parameter username: The username of the user. @@ -931,7 +1014,7 @@ extension ParseUser { extension ParseUser { /** - Saves the `ParseUser` *synchronously* and throws an error if there's an issue. + Saves the `ParseUser` *synchronously* and throws an error if there is an issue. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. @@ -943,7 +1026,7 @@ extension ParseUser { } /** - Saves the `ParseUser` *synchronously* and throws an error if there's an issue. + Saves the `ParseUser` *synchronously* and throws an error if there is an issue. - parameter ignoringCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.isAllowingCustomObjectIds = true` to allow for mixed @@ -1709,7 +1792,7 @@ public extension Sequence where Element: ParseUser { - returns: Returns a Result enum with the object if a fetch was successful or a `ParseError` if it failed. - throws: An error of `ParseError` type. - important: If an object fetched has the same objectId as current, it will automatically update the current. - - warning: The order in which users are returned are not guarenteed. You shouldn't expect results in + - warning: The order in which users are returned are not guarenteed. You should not expect results in any particular order. */ func fetchAll(includeKeys: [String]? = nil, @@ -1752,7 +1835,7 @@ public extension Sequence where Element: ParseUser { - parameter completion: The block to execute. It should have the following argument signature: `(Result<[(Result)], ParseError>)`. - important: If an object fetched has the same objectId as current, it will automatically update the current. - - warning: The order in which users are returned are not guarenteed. You shouldn't expect results in + - warning: The order in which users are returned are not guarenteed. You should not expect results in any particular order. */ func fetchAll( diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index ee6fe0651..b1532b288 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -126,7 +126,6 @@ public struct ParseConfiguration { webhookKey: String? = nil, serverURL: URL, liveQueryServerURL: URL? = nil, - allowCustomObjectId: Bool = false, allowingCustomObjectIds: Bool = false, usingTransactions: Bool = false, usingEqualQueryConstraint: Bool = false, @@ -135,7 +134,6 @@ public struct ParseConfiguration { requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheMemoryCapacity: Int = 512_000, cacheDiskCapacity: Int = 10_000_000, - migratingFromObjcSDK: Bool = false, deletingKeychainIfNeeded: Bool = false, httpAdditionalHeaders: [AnyHashable: Any]? = nil, maxConnectionAttempts: Int = 5, @@ -158,12 +156,95 @@ public struct ParseConfiguration { self.requestCachePolicy = requestCachePolicy self.cacheMemoryCapacity = cacheMemoryCapacity self.cacheDiskCapacity = cacheDiskCapacity - self.isMigratingFromObjcSDK = migratingFromObjcSDK self.isDeletingKeychainIfNeeded = deletingKeychainIfNeeded self.httpAdditionalHeaders = httpAdditionalHeaders self.maxConnectionAttempts = maxConnectionAttempts ParseStorage.shared.use(keyValueStore ?? InMemoryKeyValueStore()) } + + /** + Create a Parse Swift configuration. + - parameter applicationId: The application id for your Parse application. + - parameter clientKey: The client key for your Parse application. + - parameter masterKey: The master key for your Parse application. This key should only be + specified when using the SDK on a server. + - parameter serverURL: The server URL to connect to Parse Server. + - parameter liveQueryServerURL: The live query server URL to connect to Parse Server. + - parameter allowingCustomObjectIds: Allows objectIds to be created on the client. + side for each object. Must be enabled on the server to work. + - parameter usingTransactions: Use transactions when saving/updating multiple objects. + - parameter usingEqualQueryConstraint: Use the **$eq** query constraint when querying. + - parameter usingPostForQuery: Use **POST** instead of **GET** when making query calls. + Defaults to **false**. + - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` + protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this + this is the only store available since there is no Keychain. Linux users should replace this store with an + encrypted one. + - parameter requestCachePolicy: The default caching policy for all http requests that determines + when to return a response from the cache. Defaults to `useProtocolCachePolicy`. See Apple's [documentation](https://developer.apple.com/documentation/foundation/url_loading_system/accessing_cached_data) + for more info. + - parameter cacheMemoryCapacity: The memory capacity of the cache, in bytes. Defaults to 512KB. + - parameter cacheDiskCapacity: The disk capacity of the cache, in bytes. Defaults to 10MB. + - parameter migratingFromObjcSDK: If your app previously used the iOS Objective-C SDK, setting this value + to **true** will attempt to migrate relevant data stored in the Keychain to ParseSwift. Defaults to **false**. + - parameter deletingKeychainIfNeeded: Deletes the Parse Keychain when the app is running for the first time. + Defaults to **false**. + - parameter httpAdditionalHeaders: A dictionary of additional headers to send with requests. See Apple's + [documentation](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders) + for more info. + - parameter maxConnectionAttempts: Maximum number of times to try to connect to Parse Server. + Defaults to 5. + - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. + Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. + It should have the following argument signature: `(challenge: URLAuthenticationChallenge, + completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void`. + See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. + - warning: `usingTransactions` is experimental. + - warning: It is recomended to only specify `masterKey` when using the SDK on a server. Do not use this key on the client. + - warning: Setting `usingPostForQuery` to **true** will require all queries to access the server instead of following the `requestCachePolicy`. + */ + @available(*, deprecated, message: "Remove the migratingFromObjcSDK argument") + public init(applicationId: String, + clientKey: String? = nil, + masterKey: String? = nil, + webhookKey: String? = nil, + serverURL: URL, + liveQueryServerURL: URL? = nil, + allowingCustomObjectIds: Bool = false, + usingTransactions: Bool = false, + usingEqualQueryConstraint: Bool = false, + usingPostForQuery: Bool = false, + keyValueStore: ParseKeyValueStore? = nil, + requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, + cacheMemoryCapacity: Int = 512_000, + cacheDiskCapacity: Int = 10_000_000, + migratingFromObjcSDK: Bool = false, + deletingKeychainIfNeeded: Bool = false, + httpAdditionalHeaders: [AnyHashable: Any]? = nil, + maxConnectionAttempts: Int = 5, + authentication: ((URLAuthenticationChallenge, + (URLSession.AuthChallengeDisposition, + URLCredential?) -> Void) -> Void)? = nil) { + self.init(applicationId: applicationId, + clientKey: clientKey, + masterKey: masterKey, + webhookKey: webhookKey, + serverURL: serverURL, + liveQueryServerURL: liveQueryServerURL, + allowingCustomObjectIds: allowingCustomObjectIds, + usingTransactions: usingTransactions, + usingEqualQueryConstraint: usingEqualQueryConstraint, + usingPostForQuery: usingPostForQuery, + keyValueStore: keyValueStore, + requestCachePolicy: requestCachePolicy, + cacheMemoryCapacity: cacheMemoryCapacity, + cacheDiskCapacity: cacheDiskCapacity, + deletingKeychainIfNeeded: deletingKeychainIfNeeded, + httpAdditionalHeaders: httpAdditionalHeaders, + maxConnectionAttempts: maxConnectionAttempts, + authentication: authentication) + self.isMigratingFromObjcSDK = migratingFromObjcSDK + } } /** @@ -193,7 +274,7 @@ public struct ParseSwift { // All migrations from previous versions to current should occur here: #if !os(Linux) && !os(Android) && !os(Windows) if previousSDKVersion < oneNineEightSDKVersion { - // Old macOS Keychain can't be used because it's global to all apps. + // Old macOS Keychain cannot be used because it is global to all apps. _ = KeychainStore.old KeychainStore.shared.copy(keychain: KeychainStore.old) // Need to delete the old Keychain because a new one is created with bundleId. @@ -235,8 +316,7 @@ public struct ParseSwift { #if !os(Linux) && !os(Android) && !os(Windows) if configuration.isMigratingFromObjcSDK { - if let identifier = Bundle.main.bundleIdentifier { - let objcParseKeychain = KeychainStore(service: "\(identifier).com.parse.sdk") + if let objcParseKeychain = KeychainStore.objectiveC { guard let installationId: String = objcParseKeychain.object(forKey: "installationId"), BaseParseInstallation.current?.installationId != installationId else { return @@ -251,6 +331,85 @@ public struct ParseSwift { #endif } + /** + Configure the Parse Swift client. This should only be used when starting your app. Typically in the + `application(... didFinishLaunchingWithOptions launchOptions...)`. + - parameter applicationId: The application id for your Parse application. + - parameter clientKey: The client key for your Parse application. + - parameter masterKey: The master key for your Parse application. This key should only be + specified when using the SDK on a server. + - parameter serverURL: The server URL to connect to Parse Server. + - parameter liveQueryServerURL: The live query server URL to connect to Parse Server. + - parameter allowingCustomObjectIds: Allows objectIds to be created on the client. + side for each object. Must be enabled on the server to work. + - parameter usingTransactions: Use transactions when saving/updating multiple objects. + - parameter usingEqualQueryConstraint: Use the **$eq** query constraint when querying. + - parameter usingPostForQuery: Use **POST** instead of **GET** when making query calls. + Defaults to **false**. + - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` + protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this + this is the only store available since there is no Keychain. Linux users should replace this store with an + encrypted one. + - parameter requestCachePolicy: The default caching policy for all http requests that determines + when to return a response from the cache. Defaults to `useProtocolCachePolicy`. See Apple's [documentation](https://developer.apple.com/documentation/foundation/url_loading_system/accessing_cached_data) + for more info. + - parameter cacheMemoryCapacity: The memory capacity of the cache, in bytes. Defaults to 512KB. + - parameter cacheDiskCapacity: The disk capacity of the cache, in bytes. Defaults to 10MB. + - parameter deletingKeychainIfNeeded: Deletes the Parse Keychain when the app is running for the first time. + Defaults to **false**. + - parameter httpAdditionalHeaders: A dictionary of additional headers to send with requests. See Apple's + [documentation](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders) + for more info. + - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. + Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. + It should have the following argument signature: `(challenge: URLAuthenticationChallenge, + completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void`. + See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. + - warning: `usingTransactions` is experimental. + - warning: It is recomended to only specify `masterKey` when using the SDK on a server. Do not use this key on the client. + - warning: Setting `usingPostForQuery` to **true** will require all queries to access the server instead of following the `requestCachePolicy`. + */ + static public func initialize( + applicationId: String, + clientKey: String? = nil, + masterKey: String? = nil, + serverURL: URL, + liveQueryServerURL: URL? = nil, + allowingCustomObjectIds: Bool = false, + usingTransactions: Bool = false, + usingEqualQueryConstraint: Bool = false, + usingPostForQuery: Bool = false, + keyValueStore: ParseKeyValueStore? = nil, + requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, + cacheMemoryCapacity: Int = 512_000, + cacheDiskCapacity: Int = 10_000_000, + deletingKeychainIfNeeded: Bool = false, + httpAdditionalHeaders: [AnyHashable: Any]? = nil, + maxConnectionAttempts: Int = 5, + authentication: ((URLAuthenticationChallenge, + (URLSession.AuthChallengeDisposition, + URLCredential?) -> Void) -> Void)? = nil + ) { + let configuration = ParseConfiguration(applicationId: applicationId, + clientKey: clientKey, + masterKey: masterKey, + serverURL: serverURL, + liveQueryServerURL: liveQueryServerURL, + allowingCustomObjectIds: allowingCustomObjectIds, + usingTransactions: usingTransactions, + usingEqualQueryConstraint: usingEqualQueryConstraint, + usingPostForQuery: usingPostForQuery, + keyValueStore: keyValueStore, + requestCachePolicy: requestCachePolicy, + cacheMemoryCapacity: cacheMemoryCapacity, + cacheDiskCapacity: cacheDiskCapacity, + deletingKeychainIfNeeded: deletingKeychainIfNeeded, + httpAdditionalHeaders: httpAdditionalHeaders, + maxConnectionAttempts: maxConnectionAttempts, + authentication: authentication) + initialize(configuration: configuration) + } + /** Configure the Parse Swift client. This should only be used when starting your app. Typically in the `application(... didFinishLaunchingWithOptions launchOptions...)`. @@ -291,6 +450,7 @@ public struct ParseSwift { - warning: It is recomended to only specify `masterKey` when using the SDK on a server. Do not use this key on the client. - warning: Setting `usingPostForQuery` to **true** will require all queries to access the server instead of following the `requestCachePolicy`. */ + @available(*, deprecated, message: "Remove the migratingFromObjcSDK argument") static public func initialize( applicationId: String, clientKey: String? = nil, @@ -313,24 +473,25 @@ public struct ParseSwift { (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? = nil ) { - initialize(configuration: .init(applicationId: applicationId, - clientKey: clientKey, - masterKey: masterKey, - serverURL: serverURL, - liveQueryServerURL: liveQueryServerURL, - allowingCustomObjectIds: allowingCustomObjectIds, - usingTransactions: usingTransactions, - usingEqualQueryConstraint: usingEqualQueryConstraint, - usingPostForQuery: usingPostForQuery, - keyValueStore: keyValueStore, - requestCachePolicy: requestCachePolicy, - cacheMemoryCapacity: cacheMemoryCapacity, - cacheDiskCapacity: cacheDiskCapacity, - migratingFromObjcSDK: migratingFromObjcSDK, - deletingKeychainIfNeeded: deletingKeychainIfNeeded, - httpAdditionalHeaders: httpAdditionalHeaders, - maxConnectionAttempts: maxConnectionAttempts, - authentication: authentication)) + var configuration = ParseConfiguration(applicationId: applicationId, + clientKey: clientKey, + masterKey: masterKey, + serverURL: serverURL, + liveQueryServerURL: liveQueryServerURL, + allowingCustomObjectIds: allowingCustomObjectIds, + usingTransactions: usingTransactions, + usingEqualQueryConstraint: usingEqualQueryConstraint, + usingPostForQuery: usingPostForQuery, + keyValueStore: keyValueStore, + requestCachePolicy: requestCachePolicy, + cacheMemoryCapacity: cacheMemoryCapacity, + cacheDiskCapacity: cacheDiskCapacity, + deletingKeychainIfNeeded: deletingKeychainIfNeeded, + httpAdditionalHeaders: httpAdditionalHeaders, + maxConnectionAttempts: maxConnectionAttempts, + authentication: authentication) + configuration.isMigratingFromObjcSDK = migratingFromObjcSDK + initialize(configuration: configuration) } internal static func initialize(applicationId: String, @@ -367,11 +528,11 @@ public struct ParseSwift { requestCachePolicy: requestCachePolicy, cacheMemoryCapacity: cacheMemoryCapacity, cacheDiskCapacity: cacheDiskCapacity, - migratingFromObjcSDK: migratingFromObjcSDK, deletingKeychainIfNeeded: deletingKeychainIfNeeded, httpAdditionalHeaders: httpAdditionalHeaders, maxConnectionAttempts: maxConnectionAttempts, authentication: authentication) + configuration.isMigratingFromObjcSDK = migratingFromObjcSDK configuration.isTestingSDK = testing initialize(configuration: configuration) } @@ -415,10 +576,7 @@ public struct ParseSwift { - warning: The keychain cannot be recovered after deletion. */ static public func deleteObjectiveCKeychain() throws { - if let identifier = Bundle.main.bundleIdentifier { - let objcParseKeychain = KeychainStore(service: "\(identifier).com.parse.sdk") - try objcParseKeychain.deleteAll() - } + try KeychainStore.objectiveC?.deleteAll() } #endif diff --git a/Sources/ParseSwift/Protocols/CloudObservable.swift b/Sources/ParseSwift/Protocols/CloudObservable.swift index dbab40b65..cbc5c801d 100644 --- a/Sources/ParseSwift/Protocols/CloudObservable.swift +++ b/Sources/ParseSwift/Protocols/CloudObservable.swift @@ -24,7 +24,7 @@ public protocol CloudObservable: ObservableObject { /** Calls a Cloud Code function *asynchronously* and updates the view model - when the result of it's execution. + when the result of it is execution. - parameter options: A set of header options sent to the server. Defaults to an empty set. */ func runFunction(options: API.Options) diff --git a/Sources/ParseSwift/Protocols/Objectable.swift b/Sources/ParseSwift/Protocols/Objectable.swift index 44c99c731..5d9f9bd4c 100644 --- a/Sources/ParseSwift/Protocols/Objectable.swift +++ b/Sources/ParseSwift/Protocols/Objectable.swift @@ -56,7 +56,7 @@ extension Objectable { static func createHash(_ object: Encodable) throws -> String { let encoded = try ParseCoding.parseEncoder().encode(object) guard let hashString = String(data: encoded, encoding: .utf8) else { - throw ParseError(code: .unknownError, message: "Couldn't create hash") + throw ParseError(code: .unknownError, message: "Could not create hash") } return hashString } diff --git a/Sources/ParseSwift/Protocols/ParseCloud+async.swift b/Sources/ParseSwift/Protocols/ParseCloud+async.swift index 79cf2ceaf..9cb70124c 100644 --- a/Sources/ParseSwift/Protocols/ParseCloud+async.swift +++ b/Sources/ParseSwift/Protocols/ParseCloud+async.swift @@ -14,7 +14,7 @@ public extension ParseCloud { // MARK: Aysnc/Await /** - Calls a Cloud Code function *asynchronously* and returns a result of it's execution. + Calls a Cloud Code function *asynchronously* and returns a result of it is execution. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: The return type. - throws: An error of type `ParseError`. diff --git a/Sources/ParseSwift/Protocols/ParseCloud+combine.swift b/Sources/ParseSwift/Protocols/ParseCloud+combine.swift index 64dc031fb..641606256 100644 --- a/Sources/ParseSwift/Protocols/ParseCloud+combine.swift +++ b/Sources/ParseSwift/Protocols/ParseCloud+combine.swift @@ -15,7 +15,7 @@ public extension ParseCloud { // MARK: Combine /** - Calls a Cloud Code function *asynchronously* and returns a result of it's execution. + Calls a Cloud Code function *asynchronously* and returns a result of it is execution. Publishes when complete. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. diff --git a/Sources/ParseSwift/Protocols/ParseCloud.swift b/Sources/ParseSwift/Protocols/ParseCloud.swift index 7316aeffd..6b4a56fc8 100644 --- a/Sources/ParseSwift/Protocols/ParseCloud.swift +++ b/Sources/ParseSwift/Protocols/ParseCloud.swift @@ -29,7 +29,7 @@ public protocol ParseCloud: ParseCloudTypeable, Hashable { extension ParseCloud { /** - Calls a Cloud Code function *synchronously* and returns a result of it's execution. + Calls a Cloud Code function *synchronously* and returns a result of it is execution. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a `Decodable` type. - throws: An error of type `ParseError`. @@ -39,7 +39,7 @@ extension ParseCloud { } /** - Calls a Cloud Code function *asynchronously* and returns a result of it's execution. + Calls a Cloud Code function *asynchronously* and returns a result of it is execution. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: A block that will be called when the Cloud Code completes or fails. diff --git a/Sources/ParseSwift/Protocols/QueryObservable.swift b/Sources/ParseSwift/Protocols/QueryObservable.swift index 5d8ae0891..f0aac0523 100644 --- a/Sources/ParseSwift/Protocols/QueryObservable.swift +++ b/Sources/ParseSwift/Protocols/QueryObservable.swift @@ -68,7 +68,7 @@ public protocol QueryObservable: ObservableObject { exposed to the public. - parameter pipeline: A pipeline of stages to process query. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - warning: This hasn't been tested thoroughly. + - warning: This has not been tested thoroughly. */ func aggregate(_ pipeline: [[String: Encodable]], options: API.Options) diff --git a/Sources/ParseSwift/Storage/KeychainStore.swift b/Sources/ParseSwift/Storage/KeychainStore.swift index a75e86f83..39bae762f 100644 --- a/Sources/ParseSwift/Storage/KeychainStore.swift +++ b/Sources/ParseSwift/Storage/KeychainStore.swift @@ -32,6 +32,13 @@ struct KeychainStore: SecureStorage { let synchronizationQueue: DispatchQueue private let keychainQueryTemplate: [String: String] static var shared = KeychainStore() + static var objectiveC: KeychainStore? { + if let identifier = Bundle.main.bundleIdentifier { + return KeychainStore(service: "\(identifier).com.parse.sdk") + } else { + return nil + } + } // This Keychain was used by SDK <= 1.9.7 static var old = KeychainStore(service: "shared") diff --git a/Sources/ParseSwift/Storage/ParseFileManager.swift b/Sources/ParseSwift/Storage/ParseFileManager.swift index 29179f28b..b6b4f93bb 100644 --- a/Sources/ParseSwift/Storage/ParseFileManager.swift +++ b/Sources/ParseSwift/Storage/ParseFileManager.swift @@ -85,7 +85,7 @@ public struct ParseFileManager { } /// Creates an instance of `ParseFileManager`. - /// - returns: If an instance can't be created, nil is returned. + /// - returns: If an instance cannot be created, nil is returned. public init?() { #if os(Linux) || os(Android) || os(Windows) let applicationId = ParseSwift.configuration.applicationId @@ -113,7 +113,7 @@ public struct ParseFileManager { synchronizationQueue.async { do { guard let data = string.data(using: .utf8) else { - completion(ParseError(code: .unknownError, message: "Couldn't convert string to utf8")) + completion(ParseError(code: .unknownError, message: "Could not convert string to utf8")) return } try data.write(to: filePath, options: self.defaultDataWritingOptions) diff --git a/Sources/ParseSwift/Storage/ParseKeyValueStore.swift b/Sources/ParseSwift/Storage/ParseKeyValueStore.swift index 73bb634a7..182c0e639 100644 --- a/Sources/ParseSwift/Storage/ParseKeyValueStore.swift +++ b/Sources/ParseSwift/Storage/ParseKeyValueStore.swift @@ -68,7 +68,7 @@ extension KeychainStore: ParseKeyValueStore { func deleteAll() throws { if !removeAllObjects() { - throw ParseError(code: .objectNotFound, message: "Couldn't delete all objects in Keychain") + throw ParseError(code: .objectNotFound, message: "Could not delete all objects in Keychain") } } @@ -79,7 +79,7 @@ extension KeychainStore: ParseKeyValueStore { func set(_ object: T, for key: String) throws where T: Encodable { if !set(object: object, forKey: key) { throw ParseError(code: .unknownError, - message: "Couldn't save object: \(object) key \"\(key)\" in Keychain") + message: "Could not save object: \(object) key \"\(key)\" in Keychain") } } } diff --git a/Sources/ParseSwift/Storage/ParseStorage.swift b/Sources/ParseSwift/Storage/ParseStorage.swift index 3e553387e..f4697f982 100644 --- a/Sources/ParseSwift/Storage/ParseStorage.swift +++ b/Sources/ParseSwift/Storage/ParseStorage.swift @@ -17,7 +17,10 @@ struct ParseStorage { private mutating func requireBackingStore() { guard backingStore != nil else { - print("You can't use ParseStorage without a backing store. An in-memory store is being used as a fallback.") + print(""" + You cannot use ParseStorage without a backing store. + An in-memory store is being used as a fallback. + """) return } } diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index fb4508dec..f48c5793c 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -323,14 +323,14 @@ extension ParseACL { aclController = controller } else { throw ParseError(code: .unknownError, - message: "Default ACL can't be found in Keychain. You should `setDefaultACL` first") + message: "Default ACL cannot be found in Keychain. You should `setDefaultACL` first") } #else if let controller: DefaultACL = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.defaultACL) { aclController = controller } else { throw ParseError(code: .unknownError, - message: "Default ACL can't be found in Keychain. You should `setDefaultACL` first") + message: "Default ACL cannot be found in Keychain. You should `setDefaultACL` first") } #endif @@ -382,7 +382,7 @@ extension ParseACL { guard let currentUser = BaseParseUser.current, let currentUserObjectId = currentUser.objectId else { - throw ParseError(code: .missingObjectId, message: "Can't set defaultACL with no current user") + throw ParseError(code: .missingObjectId, message: "Cannot set defaultACL with no current user") } let modifiedACL: ParseACL? diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift index b3b381756..3b0347e10 100644 --- a/Sources/ParseSwift/Types/ParseError.swift +++ b/Sources/ParseSwift/Types/ParseError.swift @@ -48,12 +48,12 @@ public struct ParseError: ParseTypeable, Swift.Error { case connectionFailed = 100 /** - Object doesn't exist, or has an incorrect password. + Object does not exist, or has an incorrect password. */ case objectNotFound = 101 /** - You tried to find values matching a datatype that doesn't + You tried to find values matching a datatype that does not support exact database matching, like an array or a dictionary. */ case invalidQuery = 102 @@ -117,7 +117,7 @@ public struct ParseError: ParseTypeable, Swift.Error { case objectTooLarge = 116 /** - That operation isn't allowed for clients. + That operation is not allowed for clients. */ case operationForbidden = 119 diff --git a/Sources/ParseSwift/Types/ParseFile+async.swift b/Sources/ParseSwift/Types/ParseFile+async.swift index 5f03ee5df..d08d43c2d 100644 --- a/Sources/ParseSwift/Types/ParseFile+async.swift +++ b/Sources/ParseSwift/Types/ParseFile+async.swift @@ -33,7 +33,7 @@ public extension ParseFile { /** Fetches a file with given url *asynchronously*. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - returns: A fetched `ParseFile`. @@ -53,7 +53,7 @@ public extension ParseFile { /** Creates a file with given data *asynchronously* and executes the given callback block. - A name will be assigned to it by the server. If the file hasn't been downloaded, it will automatically + A name will be assigned to it by the server. If the file has not been downloaded, it will automatically be downloaded before saved. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A saved `ParseFile`. @@ -68,10 +68,10 @@ public extension ParseFile { /** Creates a file with given data *asynchronously* and executes the given callback block. - A name will be assigned to it by the server. If the file hasn't been downloaded, it will automatically + A name will be assigned to it by the server. If the file has not been downloaded, it will automatically be downloaded before saved. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - returns: A ParsFile. diff --git a/Sources/ParseSwift/Types/ParseFile+combine.swift b/Sources/ParseSwift/Types/ParseFile+combine.swift index dc996786a..895cd30b3 100644 --- a/Sources/ParseSwift/Types/ParseFile+combine.swift +++ b/Sources/ParseSwift/Types/ParseFile+combine.swift @@ -33,7 +33,7 @@ public extension ParseFile { /** Fetches a file with given url *synchronously*. Publishes when complete. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - returns: A publisher that eventually produces a single value and then finishes or fails. @@ -53,7 +53,7 @@ public extension ParseFile { /** Creates a file with given data *asynchronously* and executes the given callback block. Publishes when complete. - A name will be assigned to it by the server. If the file hasn't been downloaded, it will automatically + A name will be assigned to it by the server. If the file has not been downloaded, it will automatically be downloaded before saved. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. @@ -67,10 +67,10 @@ public extension ParseFile { /** Creates a file with given data *asynchronously* and executes the given callback block. - A name will be assigned to it by the server. If the file hasn't been downloaded, it will automatically + A name will be assigned to it by the server. If the file has not been downloaded, it will automatically be downloaded before saved. Publishes when complete. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - returns: A publisher that eventually produces a single value and then finishes or fails. diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index c1a361b57..971f9d48e 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -263,7 +263,7 @@ extension ParseFile { } - parameter options: A set of header options sent to the server. Defaults to an empty set. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - parameter stream: An input file stream. @@ -297,7 +297,7 @@ extension ParseFile { /** Creates a file with given data *synchronously*. A name will be assigned to it by the server. - If the file hasn't been downloaded, it will automatically be downloaded before saved. + If the file has not been downloaded, it will automatically be downloaded before saved. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A saved `ParseFile`. */ @@ -324,7 +324,7 @@ extension ParseFile { /** Creates a file with given data *synchronously*. A name will be assigned to it by the server. - If the file hasn't been downloaded, it will automatically be downloaded before saved. + If the file has not been downloaded, it will automatically be downloaded before saved. **Checking progress** @@ -358,7 +358,7 @@ extension ParseFile { - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after synchronous completion. Defailts to .main. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - returns: A saved `ParseFile`. @@ -394,7 +394,7 @@ extension ParseFile { /** Creates a file with given data *asynchronously* and executes the given callback block. - A name will be assigned to it by the server. If the file hasn't been downloaded, it will automatically + A name will be assigned to it by the server. If the file has not been downloaded, it will automatically be downloaded before saved. **Checking progress** @@ -430,7 +430,7 @@ extension ParseFile { - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - parameter completion: A block that will be called when file saves or fails. @@ -615,7 +615,7 @@ extension ParseFile { - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after synchronous completion. Defaults to .main. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - returns: A saved `ParseFile`. @@ -678,7 +678,7 @@ extension ParseFile { - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - - parameter progress: A block that will be called when file updates it's progress. + - parameter progress: A block that will be called when file updates it is progress. It should have the following argument signature: `(task: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`. - parameter completion: A block that will be called when file fetches or fails. diff --git a/Sources/ParseSwift/Types/ParseHealth.swift b/Sources/ParseSwift/Types/ParseHealth.swift index 88fea2cb6..d045c4fed 100644 --- a/Sources/ParseSwift/Types/ParseHealth.swift +++ b/Sources/ParseSwift/Types/ParseHealth.swift @@ -14,7 +14,7 @@ import Foundation public struct ParseHealth: ParseTypeable { /** - Calls the health check function *synchronously* and returns a result of it's execution. + Calls the health check function *synchronously* and returns a result of it is execution. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns the status of the server. - throws: An error of type `ParseError`. @@ -28,7 +28,7 @@ public struct ParseHealth: ParseTypeable { } /** - Calls the health check function *asynchronously* and returns a result of it's execution. + Calls the health check function *asynchronously* and returns a result of it is execution. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: A block that will be called when the health check completes or fails. diff --git a/Sources/ParseSwift/Types/ParseOperation.swift b/Sources/ParseSwift/Types/ParseOperation.swift index 5b55d3342..6735a00b1 100644 --- a/Sources/ParseSwift/Types/ParseOperation.swift +++ b/Sources/ParseSwift/Types/ParseOperation.swift @@ -82,7 +82,7 @@ public struct ParseOperation: Savable where T: ParseObject { /** An operation that adds a new element to an array field, - only if it wasn't already present. + only if it was not already present. - Parameters: - key: The key of the object. - objects: The field of objects. @@ -96,7 +96,7 @@ public struct ParseOperation: Savable where T: ParseObject { /** An operation that adds a new element to an array field, - only if it wasn't already present. + only if it was not already present. - Parameters: - key: A tuple consisting of the key and the respective KeyPath of the object. - objects: The field of objects. @@ -114,7 +114,7 @@ public struct ParseOperation: Savable where T: ParseObject { /** An operation that adds a new element to an array field, - only if it wasn't already present. + only if it was not already present. - Parameters: - key: A tuple consisting of the key and the respective KeyPath of the object. - objects: The field of objects. @@ -378,7 +378,7 @@ public struct ParseOperation: Savable where T: ParseObject { // MARK: Savable extension ParseOperation { /** - Saves the operations on the `ParseObject` *synchronously* and throws an error if there's an issue. + Saves the operations on the `ParseObject` *synchronously* and throws an error if there is an issue. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. @@ -387,7 +387,7 @@ extension ParseOperation { */ public func save(options: API.Options = []) throws -> T { guard target.objectId != nil else { - throw ParseError(code: .missingObjectId, message: "ParseObject isn't saved.") + throw ParseError(code: .missingObjectId, message: "ParseObject is not saved.") } return try saveCommand() .execute(options: options) @@ -408,7 +408,7 @@ extension ParseOperation { ) { guard target.objectId != nil else { callbackQueue.async { - let error = ParseError(code: .missingObjectId, message: "ParseObject isn't saved.") + let error = ParseError(code: .missingObjectId, message: "ParseObject is not saved.") completion(.failure(error)) } return @@ -420,7 +420,7 @@ extension ParseOperation { } } catch { callbackQueue.async { - let error = ParseError(code: .missingObjectId, message: "ParseObject isn't saved.") + let error = ParseError(code: .missingObjectId, message: "ParseObject is not saved.") completion(.failure(error)) } } diff --git a/Sources/ParseSwift/Types/ParsePush.swift b/Sources/ParseSwift/Types/ParsePush.swift index 33a6275a8..4483afdf7 100644 --- a/Sources/ParseSwift/Types/ParsePush.swift +++ b/Sources/ParseSwift/Types/ParsePush.swift @@ -34,7 +34,7 @@ public struct ParsePush: ParseTypeable { If the notification cannot be delivered to the device, will retry until it expires. An expiry of **0** indicates that the notification expires immediately, therefore no retries will be attempted. - - note: This shouldn't be set directly using a **Date** type. Instead it should + - note: This should not be set directly using a **Date** type. Instead it should be set using `expirationDate`. - warning: Cannot send a notification with this valuel and `expirationInterval` both set. */ diff --git a/Sources/ParseSwift/Types/ParsePushStatus.swift b/Sources/ParseSwift/Types/ParsePushStatus.swift index 7bd28d948..7b9140200 100644 --- a/Sources/ParseSwift/Types/ParsePushStatus.swift +++ b/Sources/ParseSwift/Types/ParsePushStatus.swift @@ -101,7 +101,7 @@ public struct ParsePushStatus: ParsePushStatusable { } catch { let payloadString = try values.decode(String.self, forKey: .payload) guard let payloadData = payloadString.data(using: .utf8) else { - throw ParseError(code: .unknownError, message: "Couldn't decode payload") + throw ParseError(code: .unknownError, message: "Could not decode payload") } payload = try ParseCoding.jsonDecoder().decode(PayloadType.self, from: payloadData) } @@ -111,7 +111,7 @@ public struct ParsePushStatus: ParsePushStatusable { } catch { let queryString = try values.decode(String.self, forKey: .query) guard let queryData = queryString.data(using: .utf8) else { - throw ParseError(code: .unknownError, message: "Couldn't decode query") + throw ParseError(code: .unknownError, message: "Could not decode query") } query = try ParseCoding.jsonDecoder().decode(QueryWhere.self, from: queryData) } diff --git a/Sources/ParseSwift/Types/ParseSchema.swift b/Sources/ParseSwift/Types/ParseSchema.swift index 3d2667996..15531e532 100644 --- a/Sources/ParseSwift/Types/ParseSchema.swift +++ b/Sources/ParseSwift/Types/ParseSchema.swift @@ -104,7 +104,7 @@ public extension ParseSchema { return addRelation(name, options: options) default: throw ParseError(code: .unknownError, - message: "The type \"\(type)\" isn't supported by this method") + message: "The type \"\(type)\" is not supported by this method") } } diff --git a/Sources/ParseSwift/Types/ParseVersion.swift b/Sources/ParseSwift/Types/ParseVersion.swift index ba90531ff..5a1ecd1fb 100644 --- a/Sources/ParseSwift/Types/ParseVersion.swift +++ b/Sources/ParseSwift/Types/ParseVersion.swift @@ -48,7 +48,7 @@ public struct ParseVersion: ParseTypeable, Comparable { init(_ string: String?) throws { guard let newString = string else { throw ParseError(code: .unknownError, - message: "Can't initialize with nil value.") + message: "Cannot initialize with nil value.") } self.string = newString } diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index c1948d873..5da83add2 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -1046,7 +1046,7 @@ extension Query: Queryable { - parameter pipeline: A pipeline of stages to process query. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - - warning: This hasn't been tested thoroughly. + - warning: This has not been tested thoroughly. - returns: Returns the `ParseObject`s that match the query. */ public func aggregate(_ pipeline: [[String: Encodable]], @@ -1063,7 +1063,7 @@ extension Query: Queryable { guard let encoded = try? ParseCoding.jsonEncoder() .encode(self.`where`), let whereString = String(data: encoded, encoding: .utf8) else { - throw ParseError(code: .unknownError, message: "Can't decode where to String.") + throw ParseError(code: .unknownError, message: "Cannot decode where to String.") } var query = self query.`where` = QueryWhere() @@ -1090,7 +1090,7 @@ extension Query: Queryable { - parameter callbackQueue: The queue to return to after completion. Default value of `.main`. - parameter completion: The block to execute. It should have the following argument signature: `(Result<[ParseObject], ParseError>)`. - - warning: This hasn't been tested thoroughly. + - warning: This has not been tested thoroughly. */ public func aggregate(_ pipeline: [[String: Encodable]], options: API.Options = [], @@ -1111,7 +1111,7 @@ extension Query: Queryable { guard let encoded = try? ParseCoding.jsonEncoder() .encode(self.`where`), let whereString = String(data: encoded, encoding: .utf8) else { - let error = ParseError(code: .unknownError, message: "Can't decode where to String.") + let error = ParseError(code: .unknownError, message: "Cannot decode where to String.") callbackQueue.async { completion(.failure(error)) } @@ -1176,7 +1176,7 @@ extension Query: Queryable { guard let encoded = try? ParseCoding.jsonEncoder() .encode(self.`where`), let whereString = String(data: encoded, encoding: .utf8) else { - throw ParseError(code: .unknownError, message: "Can't decode where to String.") + throw ParseError(code: .unknownError, message: "Cannot decode where to String.") } var query = self query.`where` = QueryWhere() @@ -1236,7 +1236,7 @@ extension Query: Queryable { guard let encoded = try? ParseCoding.jsonEncoder() .encode(self.`where`), let whereString = String(data: encoded, encoding: .utf8) else { - let error = ParseError(code: .unknownError, message: "Can't decode where to String.") + let error = ParseError(code: .unknownError, message: "Cannot decode where to String.") callbackQueue.async { completion(.failure(error)) } @@ -1290,7 +1290,7 @@ extension Query: Queryable { - parameter key: A field to find distinct values. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - - warning: This hasn't been tested thoroughly. + - warning: This has not been tested thoroughly. - returns: Returns the `ParseObject`s that match the query. */ public func distinct(_ key: String, @@ -1314,7 +1314,7 @@ extension Query: Queryable { - parameter callbackQueue: The queue to return to after completion. Default value of `.main`. - parameter completion: The block to execute. It should have the following argument signature: `(Result<[ParseObject], ParseError>)`. - - warning: This hasn't been tested thoroughly. + - warning: This has not been tested thoroughly. */ public func distinct(_ key: String, options: API.Options = [], @@ -1356,7 +1356,7 @@ extension Query: Queryable { - parameter usingMongoDB: Set to **true** if your Parse Server uses MongoDB. Defaults to **false**. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - - warning: This hasn't been tested thoroughly. + - warning: This has not been tested thoroughly. - returns: Returns the `ParseObject`s that match the query. - warning: MongoDB's **explain** does not conform to the traditional Parse Server response, so the `usingMongoDB` flag needs to be set for MongoDB users. See more diff --git a/Tests/ParseSwiftTests/APICommandTests.swift b/Tests/ParseSwiftTests/APICommandTests.swift index 3e25b2314..4bf1db305 100644 --- a/Tests/ParseSwiftTests/APICommandTests.swift +++ b/Tests/ParseSwiftTests/APICommandTests.swift @@ -157,7 +157,7 @@ class APICommandTests: XCTestCase { //This is how errors from the server should typically come in func testErrorFromParseServer() { - let originalError = ParseError(code: .unknownError, message: "Couldn't decode") + let originalError = ParseError(code: .unknownError, message: "Could not decode") MockURLProtocol.mockRequests { _ in do { let encoded = try JSONEncoder().encode(originalError) @@ -266,7 +266,7 @@ class APICommandTests: XCTestCase { } func testErrorHTTPReturns400NoDataFromServer() { - let originalError = ParseError(code: .unknownError, message: "Couldn't decode") + let originalError = ParseError(code: .unknownError, message: "Could not decode") MockURLProtocol.mockRequests { _ in return MockURLResponse(error: originalError) // Status code defaults to 400 } @@ -288,7 +288,7 @@ class APICommandTests: XCTestCase { } func testErrorHTTPReturns500NoDataFromServer() { - let originalError = ParseError(code: .unknownError, message: "Couldn't decode") + let originalError = ParseError(code: .unknownError, message: "Could not decode") MockURLProtocol.mockRequests { _ in var response = MockURLResponse(error: originalError) response.statusCode = 500 diff --git a/Tests/ParseSwiftTests/InitializeSDKTests.swift b/Tests/ParseSwiftTests/InitializeSDKTests.swift index 0449f2589..e2aaf2fa9 100644 --- a/Tests/ParseSwiftTests/InitializeSDKTests.swift +++ b/Tests/ParseSwiftTests/InitializeSDKTests.swift @@ -44,9 +44,8 @@ class InitializeSDKTests: XCTestCase { try super.tearDownWithError() #if !os(Linux) && !os(Android) && !os(Windows) try KeychainStore.shared.deleteAll() - if let identifier = Bundle.main.bundleIdentifier { - try KeychainStore(service: "\(identifier).com.parse.sdk").deleteAll() - } + try KeychainStore.objectiveC?.deleteAll() + try KeychainStore.old.deleteAll() URLSession.shared.configuration.urlCache?.removeAllCachedResponses() #endif try ParseStorage.shared.deleteAll() @@ -513,13 +512,12 @@ class InitializeSDKTests: XCTestCase { func testMigrateObjcSDK() { - //Set keychain the way objc sets keychain - guard let identifier = Bundle.main.bundleIdentifier else { + // Set keychain the way objc sets keychain + guard let objcParseKeychain = KeychainStore.objectiveC else { XCTFail("Should have unwrapped") return } let objcInstallationId = "helloWorld" - let objcParseKeychain = KeychainStore(service: "\(identifier).com.parse.sdk") _ = objcParseKeychain.set(object: objcInstallationId, forKey: "installationId") guard let url = URL(string: "http://localhost:1337/1") else { @@ -542,12 +540,11 @@ class InitializeSDKTests: XCTestCase { func testDeleteObjcSDKKeychain() throws { //Set keychain the way objc sets keychain - guard let identifier = Bundle.main.bundleIdentifier else { + guard let objcParseKeychain = KeychainStore.objectiveC else { XCTFail("Should have unwrapped") return } let objcInstallationId = "helloWorld" - let objcParseKeychain = KeychainStore(service: "\(identifier).com.parse.sdk") _ = objcParseKeychain.set(object: objcInstallationId, forKey: "installationId") guard let retrievedInstallationId: String? = try objcParseKeychain.get(valueFor: "installationId") else { @@ -573,12 +570,11 @@ class InitializeSDKTests: XCTestCase { func testMigrateObjcSDKMissingInstallation() { //Set keychain the way objc sets keychain - guard let identifier = Bundle.main.bundleIdentifier else { + guard let objcParseKeychain = KeychainStore.objectiveC else { XCTFail("Should have unwrapped") return } let objcInstallationId = "helloWorld" - let objcParseKeychain = KeychainStore(service: "\(identifier).com.parse.sdk") _ = objcParseKeychain.set(object: objcInstallationId, forKey: "anotherPlace") guard let url = URL(string: "http://localhost:1337/1") else { diff --git a/Tests/ParseSwiftTests/MigrateObjCSDKCombineTests.swift b/Tests/ParseSwiftTests/MigrateObjCSDKCombineTests.swift new file mode 100644 index 000000000..1cc8e9086 --- /dev/null +++ b/Tests/ParseSwiftTests/MigrateObjCSDKCombineTests.swift @@ -0,0 +1,966 @@ +// +// MigrateObjCSDKCombineTests.swift +// ParseSwift +// +// Created by Corey Baker on 8/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if canImport(Combine) && !os(Linux) && !os(Android) && !os(Windows) + +import Foundation +import XCTest +import Combine +@testable import ParseSwift + +class MigrateObjCSDKCombineTests: XCTestCase { + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + //: Implement your own version of merge + func merge(with object: Self) throws -> Self { + var updated = try mergeParse(with: object) + if updated.shouldRestoreKey(\.customKey, + original: object) { + updated.customKey = object.customKey + } + return updated + } + } + + struct LoginSignupResponse: ParseUser { + + var objectId: String? + var createdAt: Date? + var sessionToken: String? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + init() { + let date = Date() + self.createdAt = date + self.updatedAt = date + self.objectId = "yarr" + self.ACL = nil + self.customKey = "blah" + self.sessionToken = "myToken" + self.username = "hello10" + self.email = "hello@parse.com" + } + } + + struct Installation: ParseInstallation { + var installationId: String? + var deviceType: String? + var deviceToken: String? + var badge: Int? + var timeZone: String? + var channels: [String]? + var appName: String? + var appIdentifier: String? + var appVersion: String? + var parseVersion: String? + var localeIdentifier: String? + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + var customKey: String? + + //: Implement your own version of merge + func merge(with object: Self) throws -> Self { + var updated = try mergeParse(with: object) + if updated.shouldRestoreKey(\.customKey, + original: object) { + updated.customKey = object.customKey + } + return updated + } + } + + let loginUserName = "hello10" + let loginPassword = "world" + let objcInstallationId = "helloWorld" + let objcSessionToken = "wow" + let objcSessionToken2 = "now" + let testInstallationObjectId = "yarr" + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + try KeychainStore.objectiveC?.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func setupObjcKeychainSDK(useOldObjCToken: Bool = false, + useBothTokens: Bool = false, + installationId: String) { + + // Set keychain the way objc sets keychain + guard let objcParseKeychain = KeychainStore.objectiveC else { + XCTFail("Should have unwrapped") + return + } + + _ = objcParseKeychain.set(object: installationId, forKey: "installationId") + if useBothTokens { + _ = objcParseKeychain.set(object: objcSessionToken, forKey: "sessionToken") + _ = objcParseKeychain.set(object: objcSessionToken2, forKey: "session_token") + } else if !useOldObjCToken { + _ = objcParseKeychain.set(object: objcSessionToken, forKey: "sessionToken") + } else { + _ = objcParseKeychain.set(object: objcSessionToken, forKey: "session_token") + } + } + + func loginNormally(sessionToken: String) throws -> User { + var loginResponse = LoginSignupResponse() + loginResponse.sessionToken = sessionToken + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + return try User.login(username: "parse", password: "user") + } + + func testLoginUsingObjCKeychain() { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Login") + + setupObjcKeychainSDK(installationId: objcInstallationId) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) + serverResponse.sessionToken = objcSessionToken + serverResponse.username = loginUserName + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + serverResponse = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: encoded) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let publisher = User.loginUsingObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { loggedIn in + XCTAssertEqual(loggedIn.updatedAt, serverResponse.updatedAt) + XCTAssertEqual(loggedIn.email, serverResponse.email) + XCTAssertEqual(loggedIn.username, self.loginUserName) + XCTAssertNil(loggedIn.password) + XCTAssertEqual(loggedIn.objectId, serverResponse.objectId) + XCTAssertEqual(loggedIn.sessionToken, self.objcSessionToken) + XCTAssertEqual(loggedIn.customKey, serverResponse.customKey) + XCTAssertNil(loggedIn.ACL) + + guard let userFromKeychain = User.current else { + XCTFail("Could not get CurrentUser from Keychain") + expectation1.fulfill() + return + } + + XCTAssertEqual(loggedIn.updatedAt, userFromKeychain.updatedAt) + XCTAssertEqual(loggedIn.email, userFromKeychain.email) + XCTAssertEqual(userFromKeychain.username, self.loginUserName) + XCTAssertNil(userFromKeychain.password) + XCTAssertEqual(loggedIn.objectId, userFromKeychain.objectId) + XCTAssertEqual(userFromKeychain.sessionToken, self.objcSessionToken) + XCTAssertEqual(loggedIn.customKey, userFromKeychain.customKey) + XCTAssertNil(userFromKeychain.ACL) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginUsingObjCKeychainOldSessionTokenKey() { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Login") + + setupObjcKeychainSDK(useOldObjCToken: true, + installationId: objcInstallationId) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) + serverResponse.sessionToken = objcSessionToken + serverResponse.username = loginUserName + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + serverResponse = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: encoded) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let publisher = User.loginUsingObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { loggedIn in + XCTAssertEqual(loggedIn.updatedAt, serverResponse.updatedAt) + XCTAssertEqual(loggedIn.email, serverResponse.email) + XCTAssertEqual(loggedIn.username, self.loginUserName) + XCTAssertNil(loggedIn.password) + XCTAssertEqual(loggedIn.objectId, serverResponse.objectId) + XCTAssertEqual(loggedIn.sessionToken, self.objcSessionToken) + XCTAssertEqual(loggedIn.customKey, serverResponse.customKey) + XCTAssertNil(loggedIn.ACL) + + guard let userFromKeychain = User.current else { + XCTFail("Could not get CurrentUser from Keychain") + expectation1.fulfill() + return + } + + XCTAssertEqual(loggedIn.updatedAt, userFromKeychain.updatedAt) + XCTAssertEqual(loggedIn.email, userFromKeychain.email) + XCTAssertEqual(userFromKeychain.username, self.loginUserName) + XCTAssertNil(userFromKeychain.password) + XCTAssertEqual(loggedIn.objectId, userFromKeychain.objectId) + XCTAssertEqual(userFromKeychain.sessionToken, self.objcSessionToken) + XCTAssertEqual(loggedIn.customKey, userFromKeychain.customKey) + XCTAssertNil(userFromKeychain.ACL) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginUsingObjCKeychainUseNewOverOld() { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Login") + + setupObjcKeychainSDK(useBothTokens: true, + installationId: objcInstallationId) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) + serverResponse.sessionToken = objcSessionToken + serverResponse.username = loginUserName + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + serverResponse = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: encoded) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let publisher = User.loginUsingObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { loggedIn in + XCTAssertEqual(loggedIn.updatedAt, serverResponse.updatedAt) + XCTAssertEqual(loggedIn.email, serverResponse.email) + XCTAssertEqual(loggedIn.username, self.loginUserName) + XCTAssertNil(loggedIn.password) + XCTAssertEqual(loggedIn.objectId, serverResponse.objectId) + XCTAssertEqual(loggedIn.sessionToken, self.objcSessionToken) + XCTAssertEqual(loggedIn.customKey, serverResponse.customKey) + XCTAssertNil(loggedIn.ACL) + + guard let userFromKeychain = User.current else { + XCTFail("Could not get CurrentUser from Keychain") + expectation1.fulfill() + return + } + + XCTAssertEqual(loggedIn.updatedAt, userFromKeychain.updatedAt) + XCTAssertEqual(loggedIn.email, userFromKeychain.email) + XCTAssertEqual(userFromKeychain.username, self.loginUserName) + XCTAssertNil(userFromKeychain.password) + XCTAssertEqual(loggedIn.objectId, userFromKeychain.objectId) + XCTAssertEqual(userFromKeychain.sessionToken, self.objcSessionToken) + XCTAssertEqual(loggedIn.customKey, userFromKeychain.customKey) + XCTAssertNil(userFromKeychain.ACL) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginUsingObjCKeychainNoKeychain() { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Login") + + let publisher = User.loginUsingObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("Objective-C")) + } else { + XCTFail("Should have thrown error") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown error") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginUsingObjCKeychainAlreadyLoggedIn() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Login") + + setupObjcKeychainSDK(installationId: objcInstallationId) + let currentUser = try loginNormally(sessionToken: objcSessionToken) + MockURLProtocol.removeAll() + + let publisher = User.loginUsingObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { returnedUser in + XCTAssertTrue(currentUser.hasSameObjectId(as: returnedUser)) + XCTAssertEqual(currentUser.sessionToken, returnedUser.sessionToken) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginUsingObjCKeychainAlreadyLoggedInWithDiffererentSession() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Login") + + setupObjcKeychainSDK(installationId: objcInstallationId) + _ = try loginNormally(sessionToken: objcSessionToken2) + MockURLProtocol.removeAll() + + let publisher = User.loginUsingObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("different")) + } else { + XCTFail("Should have thrown error") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown error") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func saveCurrentInstallation() throws { + guard var installation = Installation.current else { + XCTFail("Should unwrap") + return + } + installation.objectId = testInstallationObjectId + installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.ACL = nil + + var installationOnServer = installation + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + guard let saved = try Installation.current?.save(), + let newCurrentInstallation = Installation.current else { + XCTFail("Should have a new current installation") + return + } + XCTAssertTrue(saved.hasSameInstallationId(as: newCurrentInstallation)) + XCTAssertTrue(saved.hasSameObjectId(as: newCurrentInstallation)) + XCTAssertTrue(saved.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(saved.hasSameInstallationId(as: installationOnServer)) + XCTAssertNil(saved.ACL) + } catch { + XCTFail(error.localizedDescription) + } + } + + func testMigrateInstallation() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Migrate Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: objcInstallationId) + + var installationOnServer = installation + installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) + installationOnServer.customKey = "newValue" + installationOnServer.installationId = objcInstallationId + installationOnServer.channels = ["yo"] + installationOnServer.deviceToken = "no" + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + // Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let publisher = Installation.migrateFromObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { fetched in + guard let currentInstallation = Installation.current else { + XCTFail("Should have current installation") + return + } + XCTAssertTrue(fetched.hasSameObjectId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(fetched.hasSameInstallationId(as: installationOnServer)) + guard let fetchedCreatedAt = fetched.createdAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = installationOnServer.createdAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) + XCTAssertEqual(fetched.channels, installationOnServer.channels) + XCTAssertEqual(fetched.deviceToken, installationOnServer.deviceToken) + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, self.objcInstallationId) + XCTAssertEqual(Installation.current?.customKey, installationOnServer.customKey) + XCTAssertEqual(Installation.current?.channels, installationOnServer.channels) + XCTAssertEqual(Installation.current?.deviceToken, installationOnServer.deviceToken) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, self.objcInstallationId) + XCTAssertEqual(keychainInstallation.currentInstallation?.channels, installationOnServer.channels) + XCTAssertEqual(keychainInstallation.currentInstallation?.deviceToken, installationOnServer.deviceToken) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testMigrateInstallationDontCopyEntire() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Migrate Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: objcInstallationId) + + var installationOnServer = installation + installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) + installationOnServer.customKey = "newValue" + installationOnServer.installationId = objcInstallationId + installationOnServer.channels = ["yo"] + installationOnServer.deviceToken = "no" + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + // Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let publisher = Installation.migrateFromObjCKeychainPublisher(copyEntireInstallation: false) + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { fetched in + guard let currentInstallation = Installation.current else { + XCTFail("Should have current installation") + return + } + XCTAssertTrue(fetched.hasSameObjectId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(fetched.hasSameInstallationId(as: installation)) + guard let fetchedCreatedAt = fetched.createdAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = installationOnServer.createdAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) + XCTAssertEqual(fetched.channels, installationOnServer.channels) + XCTAssertEqual(fetched.deviceToken, installationOnServer.deviceToken) + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, installation.installationId) + XCTAssertNotEqual(Installation.current?.customKey, installationOnServer.customKey) + XCTAssertEqual(Installation.current?.channels, installationOnServer.channels) + XCTAssertEqual(Installation.current?.deviceToken, installationOnServer.deviceToken) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, installation.installationId) + XCTAssertEqual(keychainInstallation.currentInstallation?.channels, installationOnServer.channels) + XCTAssertEqual(keychainInstallation.currentInstallation?.deviceToken, installationOnServer.deviceToken) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testMigrateInstallationAlreadyMigrated() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Migrate Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId, + let savedInstallationId = installation.installationId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: savedInstallationId) + + let publisher = Installation.migrateFromObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { fetched in + guard let currentInstallation = Installation.current else { + XCTFail("Should have current installation") + return + } + XCTAssertTrue(fetched.hasSameObjectId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameObjectId(as: installation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: installation)) + guard let fetchedCreatedAt = fetched.createdAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = installation.createdAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, savedInstallationId) + XCTAssertEqual(Installation.current?.customKey, installation.customKey) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, savedInstallationId) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testMigrateInstallationServerError() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Migrate Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + setupObjcKeychainSDK(installationId: objcInstallationId) + + let serverError = ParseError(code: .objectNotFound, message: "Not found") + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(serverError) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let publisher = Installation.migrateFromObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTAssertEqual(error, serverError) + } else { + XCTFail("Should have thrown error") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown error") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testMigrateInstallationNoObjcKeychain() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Migrate Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + let publisher = Installation.migrateFromObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("find Installation")) + } else { + XCTFail("Should have thrown error") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown error") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testMigrateInstallationNoCurrentInstallation() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Migrate Installation") + + setupObjcKeychainSDK(installationId: objcInstallationId) + + try ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + try KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + Installation.currentContainer.currentInstallation = nil + + let publisher = Installation.migrateFromObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("Current installation")) + } else { + XCTFail("Should have thrown error") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown error") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testDeleteObjCKeychain() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Delete ObjC Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId, + let savedInstallationId = installation.installationId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: objcInstallationId) + + var installationOnServer = installation + installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) + installationOnServer.customKey = "newValue" + installationOnServer.installationId = objcInstallationId + installationOnServer.channels = ["yo"] + installationOnServer.deviceToken = "no" + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + // Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let publisher = Installation.deleteObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { _ in + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, savedInstallationId) + XCTAssertEqual(Installation.current?.customKey, installation.customKey) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, savedInstallationId) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testDeleteObjCKeychainAlreadyMigrated() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Delete ObjC Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId, + let savedInstallationId = installation.installationId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: savedInstallationId) + + let publisher = Installation.deleteObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { _ in + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, savedInstallationId) + XCTAssertEqual(Installation.current?.customKey, installation.customKey) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, savedInstallationId) + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testDeleteObjCKeychainNoObjcKeychain() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Delete ObjC Installation") + + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + let publisher = Installation.deleteObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("find Installation")) + } else { + XCTFail("Should have thrown error") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown error") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testDeleteObjCKeychainNoCurrentInstallation() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Delete ObjC Installation") + + setupObjcKeychainSDK(installationId: objcInstallationId) + + try ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + try KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + Installation.currentContainer.currentInstallation = nil + + let publisher = Installation.deleteObjCKeychainPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("Current installation")) + } else { + XCTFail("Should have thrown error") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown error") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } +} +#endif diff --git a/Tests/ParseSwiftTests/MigrateObjCSDKTests.swift b/Tests/ParseSwiftTests/MigrateObjCSDKTests.swift new file mode 100644 index 000000000..71e9a9896 --- /dev/null +++ b/Tests/ParseSwiftTests/MigrateObjCSDKTests.swift @@ -0,0 +1,781 @@ +// +// MigrateObjCSDKTests.swift +// ParseSwift +// +// Created by Corey Baker on 8/19/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if compiler(>=5.5.2) && canImport(_Concurrency) && !os(Linux) && !os(Android) && !os(Windows) +import Foundation +import XCTest +@testable import ParseSwift + +class MigrateObjCSDKTests: XCTestCase { // swiftlint:disable:this type_body_length + + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + //: Implement your own version of merge + func merge(with object: Self) throws -> Self { + var updated = try mergeParse(with: object) + if updated.shouldRestoreKey(\.customKey, + original: object) { + updated.customKey = object.customKey + } + return updated + } + } + + struct LoginSignupResponse: ParseUser { + + var objectId: String? + var createdAt: Date? + var sessionToken: String? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + init() { + let date = Date() + self.createdAt = date + self.updatedAt = date + self.objectId = "yarr" + self.ACL = nil + self.customKey = "blah" + self.sessionToken = "myToken" + self.username = "hello10" + self.email = "hello@parse.com" + } + } + + struct Installation: ParseInstallation { + var installationId: String? + var deviceType: String? + var deviceToken: String? + var badge: Int? + var timeZone: String? + var channels: [String]? + var appName: String? + var appIdentifier: String? + var appVersion: String? + var parseVersion: String? + var localeIdentifier: String? + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + var customKey: String? + + //: Implement your own version of merge + func merge(with object: Self) throws -> Self { + var updated = try mergeParse(with: object) + if updated.shouldRestoreKey(\.customKey, + original: object) { + updated.customKey = object.customKey + } + return updated + } + } + + let loginUserName = "hello10" + let loginPassword = "world" + let objcInstallationId = "helloWorld" + let objcSessionToken = "wow" + let objcSessionToken2 = "now" + let testInstallationObjectId = "yarr" + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + try KeychainStore.objectiveC?.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func setupObjcKeychainSDK(useOldObjCToken: Bool = false, + useBothTokens: Bool = false, + installationId: String) { + + // Set keychain the way objc sets keychain + guard let objcParseKeychain = KeychainStore.objectiveC else { + XCTFail("Should have unwrapped") + return + } + + _ = objcParseKeychain.set(object: installationId, forKey: "installationId") + if useBothTokens { + _ = objcParseKeychain.set(object: objcSessionToken, forKey: "sessionToken") + _ = objcParseKeychain.set(object: objcSessionToken2, forKey: "session_token") + } else if !useOldObjCToken { + _ = objcParseKeychain.set(object: objcSessionToken, forKey: "sessionToken") + } else { + _ = objcParseKeychain.set(object: objcSessionToken, forKey: "session_token") + } + } + + func loginNormally(sessionToken: String) async throws -> User { + var loginResponse = LoginSignupResponse() + loginResponse.sessionToken = sessionToken + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + return try await User.login(username: "parse", password: "user") + } + + @MainActor + func testLoginUsingObjCKeychain() async throws { + setupObjcKeychainSDK(installationId: objcInstallationId) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) + serverResponse.sessionToken = objcSessionToken + serverResponse.username = loginUserName + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + serverResponse = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: encoded) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let loggedIn = try await User.loginUsingObjCKeychain() + XCTAssertEqual(loggedIn.updatedAt, serverResponse.updatedAt) + XCTAssertEqual(loggedIn.email, serverResponse.email) + XCTAssertEqual(loggedIn.username, loginUserName) + XCTAssertNil(loggedIn.password) + XCTAssertEqual(loggedIn.objectId, serverResponse.objectId) + XCTAssertEqual(loggedIn.sessionToken, objcSessionToken) + XCTAssertEqual(loggedIn.customKey, serverResponse.customKey) + XCTAssertNil(loggedIn.ACL) + + guard let userFromKeychain = User.current else { + XCTFail("Could not get CurrentUser from Keychain") + return + } + + XCTAssertEqual(loggedIn.updatedAt, userFromKeychain.updatedAt) + XCTAssertEqual(loggedIn.email, userFromKeychain.email) + XCTAssertEqual(userFromKeychain.username, loginUserName) + XCTAssertNil(userFromKeychain.password) + XCTAssertEqual(loggedIn.objectId, userFromKeychain.objectId) + XCTAssertEqual(userFromKeychain.sessionToken, objcSessionToken) + XCTAssertEqual(loggedIn.customKey, userFromKeychain.customKey) + XCTAssertNil(userFromKeychain.ACL) + } + + @MainActor + func testLoginUsingObjCKeychainOldSessionTokenKey() async throws { + setupObjcKeychainSDK(useOldObjCToken: true, + installationId: objcInstallationId) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) + serverResponse.sessionToken = objcSessionToken + serverResponse.username = loginUserName + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + serverResponse = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: encoded) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let loggedIn = try await User.loginUsingObjCKeychain() + XCTAssertEqual(loggedIn.updatedAt, serverResponse.updatedAt) + XCTAssertEqual(loggedIn.email, serverResponse.email) + XCTAssertEqual(loggedIn.username, loginUserName) + XCTAssertNil(loggedIn.password) + XCTAssertEqual(loggedIn.objectId, serverResponse.objectId) + XCTAssertEqual(loggedIn.sessionToken, objcSessionToken) + XCTAssertEqual(loggedIn.customKey, serverResponse.customKey) + XCTAssertNil(loggedIn.ACL) + + guard let userFromKeychain = User.current else { + XCTFail("Could not get CurrentUser from Keychain") + return + } + + XCTAssertEqual(loggedIn.updatedAt, userFromKeychain.updatedAt) + XCTAssertEqual(loggedIn.email, userFromKeychain.email) + XCTAssertEqual(userFromKeychain.username, loginUserName) + XCTAssertNil(userFromKeychain.password) + XCTAssertEqual(loggedIn.objectId, userFromKeychain.objectId) + XCTAssertEqual(userFromKeychain.sessionToken, objcSessionToken) + XCTAssertEqual(loggedIn.customKey, userFromKeychain.customKey) + XCTAssertNil(userFromKeychain.ACL) + } + + @MainActor + func testLoginUsingObjCKeychainUseNewOverOld() async throws { + setupObjcKeychainSDK(useBothTokens: true, + installationId: objcInstallationId) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) + serverResponse.sessionToken = objcSessionToken + serverResponse.username = loginUserName + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + serverResponse = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: encoded) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let loggedIn = try await User.loginUsingObjCKeychain() + XCTAssertEqual(loggedIn.updatedAt, serverResponse.updatedAt) + XCTAssertEqual(loggedIn.email, serverResponse.email) + XCTAssertEqual(loggedIn.username, loginUserName) + XCTAssertNil(loggedIn.password) + XCTAssertEqual(loggedIn.objectId, serverResponse.objectId) + XCTAssertEqual(loggedIn.sessionToken, objcSessionToken) + XCTAssertEqual(loggedIn.customKey, serverResponse.customKey) + XCTAssertNil(loggedIn.ACL) + + guard let userFromKeychain = User.current else { + XCTFail("Could not get CurrentUser from Keychain") + return + } + + XCTAssertEqual(loggedIn.updatedAt, userFromKeychain.updatedAt) + XCTAssertEqual(loggedIn.email, userFromKeychain.email) + XCTAssertEqual(userFromKeychain.username, loginUserName) + XCTAssertNil(userFromKeychain.password) + XCTAssertEqual(loggedIn.objectId, userFromKeychain.objectId) + XCTAssertEqual(userFromKeychain.sessionToken, objcSessionToken) + XCTAssertEqual(loggedIn.customKey, userFromKeychain.customKey) + XCTAssertNil(userFromKeychain.ACL) + } + + @MainActor + func testLoginUsingObjCKeychainNoKeychain() async throws { + + do { + _ = try await User.loginUsingObjCKeychain() + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Should have casted to ParseError") + return + } + XCTAssertTrue(parseError.message.contains("Objective-C")) + } + } + + @MainActor + func testLoginUsingObjCKeychainAlreadyLoggedIn() async throws { + setupObjcKeychainSDK(installationId: objcInstallationId) + let currentUser = try await loginNormally(sessionToken: objcSessionToken) + MockURLProtocol.removeAll() + let returnedUser = try await User.loginUsingObjCKeychain() + XCTAssertTrue(currentUser.hasSameObjectId(as: returnedUser)) + XCTAssertEqual(currentUser.sessionToken, returnedUser.sessionToken) + } + + @MainActor + func testLoginUsingObjCKeychainAlreadyLoggedInWithDiffererentSession() async throws { + setupObjcKeychainSDK(installationId: objcInstallationId) + _ = try await loginNormally(sessionToken: objcSessionToken2) + MockURLProtocol.removeAll() + do { + _ = try await User.loginUsingObjCKeychain() + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Should have casted to ParseError") + return + } + XCTAssertTrue(parseError.message.contains("different")) + } + } + + func saveCurrentInstallation() throws { + guard var installation = Installation.current else { + XCTFail("Should unwrap") + return + } + installation.objectId = testInstallationObjectId + installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.ACL = nil + + var installationOnServer = installation + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + guard let saved = try Installation.current?.save(), + let newCurrentInstallation = Installation.current else { + XCTFail("Should have a new current installation") + return + } + XCTAssertTrue(saved.hasSameInstallationId(as: newCurrentInstallation)) + XCTAssertTrue(saved.hasSameObjectId(as: newCurrentInstallation)) + XCTAssertTrue(saved.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(saved.hasSameInstallationId(as: installationOnServer)) + XCTAssertNil(saved.ACL) + } catch { + XCTFail(error.localizedDescription) + } + } + + @MainActor + func testMigrateInstallation() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: objcInstallationId) + + var installationOnServer = installation + installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) + installationOnServer.customKey = "newValue" + installationOnServer.installationId = objcInstallationId + installationOnServer.channels = ["yo"] + installationOnServer.deviceToken = "no" + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + // Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let fetched = try await Installation.migrateFromObjCKeychain() + guard let currentInstallation = Installation.current else { + XCTFail("Should have current installation") + return + } + XCTAssertTrue(fetched.hasSameObjectId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(fetched.hasSameInstallationId(as: installationOnServer)) + guard let fetchedCreatedAt = fetched.createdAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = installationOnServer.createdAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) + XCTAssertEqual(fetched.channels, installationOnServer.channels) + XCTAssertEqual(fetched.deviceToken, installationOnServer.deviceToken) + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, objcInstallationId) + XCTAssertEqual(Installation.current?.customKey, installationOnServer.customKey) + XCTAssertEqual(Installation.current?.channels, installationOnServer.channels) + XCTAssertEqual(Installation.current?.deviceToken, installationOnServer.deviceToken) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, objcInstallationId) + XCTAssertEqual(keychainInstallation.currentInstallation?.channels, installationOnServer.channels) + XCTAssertEqual(keychainInstallation.currentInstallation?.deviceToken, installationOnServer.deviceToken) + } + + @MainActor + func testMigrateInstallationServerError() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + setupObjcKeychainSDK(installationId: objcInstallationId) + + let serverError = ParseError(code: .objectNotFound, message: "Not found") + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(serverError) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + _ = try await Installation.migrateFromObjCKeychain() + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Should have casted to ParseError") + return + } + XCTAssertEqual(parseError, serverError) + } + } + + @MainActor + func testMigrateInstallationNoObjcKeychain() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + do { + _ = try await Installation.migrateFromObjCKeychain() + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Should have casted to ParseError") + return + } + XCTAssertTrue(parseError.message.contains("find Installation")) + } + } + + @MainActor + func testMigrateInstallationNoCurrentInstallation() async throws { + setupObjcKeychainSDK(installationId: objcInstallationId) + + try ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + try KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + Installation.currentContainer.currentInstallation = nil + + do { + _ = try await Installation.migrateFromObjCKeychain() + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Should have casted to ParseError") + return + } + XCTAssertTrue(parseError.message.contains("Current installation")) + } + } + + @MainActor + func testMigrateInstallationDontCopyEntire() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: objcInstallationId) + + var installationOnServer = installation + installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) + installationOnServer.customKey = "newValue" + installationOnServer.installationId = objcInstallationId + installationOnServer.channels = ["yo"] + installationOnServer.deviceToken = "no" + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + // Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let fetched = try await Installation.migrateFromObjCKeychain(copyEntireInstallation: false) + guard let currentInstallation = Installation.current else { + XCTFail("Should have current installation") + return + } + XCTAssertTrue(fetched.hasSameObjectId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(fetched.hasSameInstallationId(as: installation)) + guard let fetchedCreatedAt = fetched.createdAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = installationOnServer.createdAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) + XCTAssertEqual(fetched.channels, installationOnServer.channels) + XCTAssertEqual(fetched.deviceToken, installationOnServer.deviceToken) + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, installation.installationId) + XCTAssertNotEqual(Installation.current?.customKey, installationOnServer.customKey) + XCTAssertEqual(Installation.current?.channels, installationOnServer.channels) + XCTAssertEqual(Installation.current?.deviceToken, installationOnServer.deviceToken) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, installation.installationId) + XCTAssertEqual(keychainInstallation.currentInstallation?.channels, installationOnServer.channels) + XCTAssertEqual(keychainInstallation.currentInstallation?.deviceToken, installationOnServer.deviceToken) + } + + @MainActor + func testMigrateInstallationAlreadyMigrated() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId, + let savedInstallationId = installation.installationId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: savedInstallationId) + + let fetched = try await Installation.migrateFromObjCKeychain() + guard let currentInstallation = Installation.current else { + XCTFail("Should have current installation") + return + } + XCTAssertTrue(fetched.hasSameObjectId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: currentInstallation)) + XCTAssertTrue(fetched.hasSameObjectId(as: installation)) + XCTAssertTrue(fetched.hasSameInstallationId(as: installation)) + guard let fetchedCreatedAt = fetched.createdAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = installation.createdAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, savedInstallationId) + XCTAssertEqual(Installation.current?.customKey, installation.customKey) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, savedInstallationId) + } + + @MainActor + func testDeleteObjCKeychain() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId, + let savedInstallationId = installation.installationId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: objcInstallationId) + + var installationOnServer = installation + installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) + installationOnServer.customKey = "newValue" + installationOnServer.installationId = objcInstallationId + installationOnServer.channels = ["yo"] + installationOnServer.deviceToken = "no" + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + // Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + try await Installation.deleteObjCKeychain() + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, savedInstallationId) + XCTAssertEqual(Installation.current?.customKey, installation.customKey) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, savedInstallationId) + } + + @MainActor + func testDeleteObjCKeychainAlreadyMigrated() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId, + let savedInstallationId = installation.installationId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + setupObjcKeychainSDK(installationId: savedInstallationId) + + try await Installation.deleteObjCKeychain() + + // Should be updated in memory + XCTAssertEqual(Installation.current?.installationId, savedInstallationId) + XCTAssertEqual(Installation.current?.customKey, installation.customKey) + + // Should be updated in Keychain + guard let keychainInstallation: CurrentInstallationContainer + = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { + XCTFail("Should get object from Keychain") + return + } + XCTAssertEqual(keychainInstallation.currentInstallation?.installationId, savedInstallationId) + } + + @MainActor + func testDeleteObjCKeychainNoObjcKeychain() async throws { + try saveCurrentInstallation() + MockURLProtocol.removeAll() + + guard let installation = Installation.current, + let savedObjectId = installation.objectId else { + XCTFail("Should unwrap") + return + } + XCTAssertEqual(savedObjectId, self.testInstallationObjectId) + + do { + _ = try await Installation.deleteObjCKeychain() + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Should have casted to ParseError") + return + } + XCTAssertTrue(parseError.message.contains("find Installation")) + } + } + + @MainActor + func testDeleteObjCKeychainNoCurrentInstallation() async throws { + setupObjcKeychainSDK(installationId: objcInstallationId) + + try ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + try KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentInstallation) + Installation.currentContainer.currentInstallation = nil + + do { + _ = try await Installation.deleteObjCKeychain() + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Should have casted to ParseError") + return + } + XCTAssertTrue(parseError.message.contains("Current installation")) + } + } +} +#endif diff --git a/Tests/ParseSwiftTests/ParseACLTests.swift b/Tests/ParseSwiftTests/ParseACLTests.swift index 22fef2283..40ba84b47 100644 --- a/Tests/ParseSwiftTests/ParseACLTests.swift +++ b/Tests/ParseSwiftTests/ParseACLTests.swift @@ -253,7 +253,7 @@ class ParseACLTests: XCTestCase { XCTAssertNotEqual(newACL, defaultACL) defaultACL = try ParseACL.setDefaultACL(defaultACL, withAccessForCurrentUser: true) if defaultACL.getReadAccess(objectId: userId) { - XCTFail("Shouldn't have set read access because there's no current user") + XCTFail("Should not have set read access because there is no current user") } } catch { return @@ -263,7 +263,7 @@ class ParseACLTests: XCTestCase { _ = try ParseACL.setDefaultACL(newACL, withAccessForCurrentUser: true) let defaultACL = try ParseACL.defaultACL() if !defaultACL.getReadAccess(objectId: userId) { - XCTFail("Should have set defaultACL with read access even though there's no current user") + XCTFail("Should have set defaultACL with read access even though there is no current user") } } catch { return @@ -291,11 +291,11 @@ class ParseACLTests: XCTestCase { do { _ = try User.signup(username: loginUserName, password: loginPassword) } catch { - XCTFail("Couldn't signUp user: \(error)") + XCTFail("Could not signUp user: \(error)") } guard let userObjectId = User.current?.objectId else { - XCTFail("Couldn't get objectId of currentUser") + XCTFail("Could not get objectId of currentUser") return } @@ -332,11 +332,11 @@ class ParseACLTests: XCTestCase { do { _ = try User.signup(username: loginUserName, password: loginPassword) } catch { - XCTFail("Couldn't signUp user: \(error.localizedDescription)") + XCTFail("Could not signUp user: \(error.localizedDescription)") } guard let userObjectId = User.current?.objectId else { - XCTFail("Couldn't get objectId of currentUser") + XCTFail("Could not get objectId of currentUser") return } diff --git a/Tests/ParseSwiftTests/ParseAppleAsyncTests.swift b/Tests/ParseSwiftTests/ParseAppleAsyncTests.swift index d52c89a16..9e92d0e0d 100644 --- a/Tests/ParseSwiftTests/ParseAppleAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseAppleAsyncTests.swift @@ -114,7 +114,7 @@ class ParseAppleAsyncTests: XCTestCase { // swiftlint:disable:this type_body_len } guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -162,7 +162,7 @@ class ParseAppleAsyncTests: XCTestCase { // swiftlint:disable:this type_body_len XCTAssertTrue(user.apple.isLinked) } - func loginNormally() throws -> User { + func loginNormally() async throws -> User { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -173,12 +173,12 @@ class ParseAppleAsyncTests: XCTestCase { // swiftlint:disable:this type_body_len return nil } } - return try User.login(username: "parse", password: "user") + return try await User.login(username: "parse", password: "user") } @MainActor func testLink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -200,7 +200,7 @@ class ParseAppleAsyncTests: XCTestCase { // swiftlint:disable:this type_body_len } guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -215,7 +215,7 @@ class ParseAppleAsyncTests: XCTestCase { // swiftlint:disable:this type_body_len @MainActor func testLinkAuthData() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -248,11 +248,11 @@ class ParseAppleAsyncTests: XCTestCase { // swiftlint:disable:this type_body_len @MainActor func testUnlink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } diff --git a/Tests/ParseSwiftTests/ParseAppleCombineTests.swift b/Tests/ParseSwiftTests/ParseAppleCombineTests.swift index c764f530a..b6c13b57e 100644 --- a/Tests/ParseSwiftTests/ParseAppleCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseAppleCombineTests.swift @@ -116,7 +116,7 @@ class ParseAppleCombineTests: XCTestCase { // swiftlint:disable:this type_body_l } guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -232,7 +232,7 @@ class ParseAppleCombineTests: XCTestCase { // swiftlint:disable:this type_body_l } guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -314,7 +314,7 @@ class ParseAppleCombineTests: XCTestCase { // swiftlint:disable:this type_body_l MockURLProtocol.removeAll() guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } diff --git a/Tests/ParseSwiftTests/ParseAppleTests.swift b/Tests/ParseSwiftTests/ParseAppleTests.swift index 4a2395322..5be071601 100644 --- a/Tests/ParseSwiftTests/ParseAppleTests.swift +++ b/Tests/ParseSwiftTests/ParseAppleTests.swift @@ -120,7 +120,7 @@ class ParseAppleTests: XCTestCase { func testLogin() throws { var serverResponse = LoginSignupResponse() guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -176,7 +176,7 @@ class ParseAppleTests: XCTestCase { func testLoginAuthData() throws { var serverResponse = LoginSignupResponse() guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -287,7 +287,7 @@ class ParseAppleTests: XCTestCase { try loginAnonymousUser() MockURLProtocol.removeAll() guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -364,7 +364,7 @@ class ParseAppleTests: XCTestCase { let expectation1 = XCTestExpectation(description: "Login") guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -412,7 +412,7 @@ class ParseAppleTests: XCTestCase { let expectation1 = XCTestExpectation(description: "Login") guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -461,7 +461,7 @@ class ParseAppleTests: XCTestCase { let expectation1 = XCTestExpectation(description: "Login") guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } @@ -510,7 +510,7 @@ class ParseAppleTests: XCTestCase { _ = try loginNormally() MockURLProtocol.removeAll() guard let tokenData = "this".data(using: .utf8) else { - XCTFail("Couldn't convert token data to string") + XCTFail("Could not convert token data to string") return } diff --git a/Tests/ParseSwiftTests/ParseAuthenticationAsyncTests.swift b/Tests/ParseSwiftTests/ParseAuthenticationAsyncTests.swift index f63a4b873..3ea8f3e43 100644 --- a/Tests/ParseSwiftTests/ParseAuthenticationAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseAuthenticationAsyncTests.swift @@ -178,7 +178,7 @@ class ParseAuthenticationAsyncTests: XCTestCase { // swiftlint:disable:this type XCTAssertEqual(user.authData, serverResponse.authData) } - func loginNormally() throws -> User { + func loginNormally() async throws -> User { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -189,13 +189,13 @@ class ParseAuthenticationAsyncTests: XCTestCase { // swiftlint:disable:this type return nil } } - return try User.login(username: "parse", password: "user") + return try await User.login(username: "parse", password: "user") } @MainActor func testLink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() let type = TestAuth.__type diff --git a/Tests/ParseSwiftTests/ParseEncoderTests/ParseEncoderExtraTests.swift b/Tests/ParseSwiftTests/ParseEncoderTests/ParseEncoderExtraTests.swift index d3cd93cca..74f543fc5 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests/ParseEncoderExtraTests.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests/ParseEncoderExtraTests.swift @@ -57,7 +57,7 @@ class ParseEncoderTests: XCTestCase { encoder.outputFormatting = .sortedKeys guard let encoding = try? encoder.encode(object) else { - XCTFail("Couldn't get a reference encoding.") + XCTFail("Could not get a reference encoding.") return Data() } diff --git a/Tests/ParseSwiftTests/ParseEncoderTests/TestParseEncoder.swift b/Tests/ParseSwiftTests/ParseEncoderTests/TestParseEncoder.swift index 044c06c56..c978e2735 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests/TestParseEncoder.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests/TestParseEncoder.swift @@ -559,7 +559,7 @@ class TestParseEncoder: XCTestCase { ("singleCharacterAtEndX", "single_character_at_end_x"), ("thisIsAnXMLProperty", "this_is_an_xml_property"), ("single", "single"), // no underscore - ("", ""), // don't die on empty string + ("", ""), // do not die on empty string ("a", "a"), // single character ("aA", "a_a"), // two characters ("version4Thing", "version4_thing"), // numerics @@ -757,7 +757,7 @@ class TestParseEncoder: XCTestCase { } func testKeyStrategyDuplicateKeys() { - // This test is mostly to make sure we don't assert on duplicate keys + // This test is mostly to make sure we do not assert on duplicate keys struct DecodeMe5: Codable { var oneTwo: String var numberOfKeys: Int @@ -834,7 +834,7 @@ class TestParseEncoder: XCTestCase { let expectedJSON = "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".data(using: .utf8)! // Want to make sure we write out a JSON number, not the keyed encoding here. - // 1e127 is too big to fit natively in a Double, too, so want to make sure it's encoded as a Decimal. + // 1e127 is too big to fit natively in a Double, too, so want to make sure it is encoded as a Decimal. let decimal = Decimal(sign: .plus, exponent: 127, significand: Decimal(1)) _testRoundTrip(of: decimal, expectedJSON: expectedJSON) @@ -1481,7 +1481,7 @@ private struct FloatNaNPlaceholder: Codable, Equatable { let container = try decoder.singleValueContainer() let float = try container.decode(Float.self) if !float.isNaN { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Couldn't decode NaN.")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode NaN.")) } } @@ -1502,7 +1502,7 @@ private struct DoubleNaNPlaceholder: Codable, Equatable { let container = try decoder.singleValueContainer() let double = try container.decode(Double.self) if !double.isNaN { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Couldn't decode NaN.")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode NaN.")) } } diff --git a/Tests/ParseSwiftTests/ParseFacebookAsyncTests.swift b/Tests/ParseSwiftTests/ParseFacebookAsyncTests.swift index 64a40508d..5529c1dbc 100644 --- a/Tests/ParseSwiftTests/ParseFacebookAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFacebookAsyncTests.swift @@ -198,7 +198,7 @@ class ParseFacebookAsyncTests: XCTestCase { // swiftlint:disable:this type_body_ XCTAssertTrue(user.facebook.isLinked) } - func loginNormally() throws -> User { + func loginNormally() async throws -> User { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -209,13 +209,13 @@ class ParseFacebookAsyncTests: XCTestCase { // swiftlint:disable:this type_body_ return nil } } - return try User.login(username: "parse", password: "user") + return try await User.login(username: "parse", password: "user") } @MainActor func testLinkLimitedLogin() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -249,7 +249,7 @@ class ParseFacebookAsyncTests: XCTestCase { // swiftlint:disable:this type_body_ @MainActor func testLinkGraphAPILogin() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -283,7 +283,7 @@ class ParseFacebookAsyncTests: XCTestCase { // swiftlint:disable:this type_body_ @MainActor func testLinkAuthData() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -321,7 +321,7 @@ class ParseFacebookAsyncTests: XCTestCase { // swiftlint:disable:this type_body_ @MainActor func testUnlinkLimitedLogin() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() let authData = ParseFacebook @@ -360,7 +360,7 @@ class ParseFacebookAsyncTests: XCTestCase { // swiftlint:disable:this type_body_ @MainActor func testUnlinkGraphAPILogin() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() let authData = ParseFacebook diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index de6eb9b18..bb420d672 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -71,7 +71,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng } #if !os(Linux) && !os(Android) && !os(Windows) - //URL Mocker is not able to mock this in linux and tests fail, so don't run. + //URL Mocker is not able to mock this in linux and tests fail, so do not run. @MainActor func testFetch() async throws { diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index c705c8405..10701ec4c 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -682,7 +682,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length #if !os(Linux) && !os(Android) && !os(Windows) - //URL Mocker is not able to mock this in linux and tests fail, so don't run. + //URL Mocker is not able to mock this in linux and tests fail, so do not run. func testFetchFileCancelAsync() throws { // swiftlint:disable:next line_length guard let parseFileURL = URL(string: "http://localhost:1337/1/files/applicationId/7793939a2e59b98138c1bbf2412a060c_logo.svg") else { diff --git a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift index 974f6f49c..1df19673b 100644 --- a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift @@ -158,7 +158,7 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body XCTAssertTrue(user.instagram.isLinked) } - func loginNormally() throws -> User { + func loginNormally() async throws -> User { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -169,12 +169,12 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body return nil } } - return try User.login(username: "parse", password: "user") + return try await User.login(username: "parse", password: "user") } @MainActor func testLink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -206,7 +206,7 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body @MainActor func testLinkAuthData() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -240,7 +240,7 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body @MainActor func testUnlink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() let authData = ParseInstagram diff --git a/Tests/ParseSwiftTests/ParseLDAPAsyncTests.swift b/Tests/ParseSwiftTests/ParseLDAPAsyncTests.swift index 321b823f5..ddb51332a 100644 --- a/Tests/ParseSwiftTests/ParseLDAPAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseLDAPAsyncTests.swift @@ -159,7 +159,7 @@ class ParseLDAPAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertTrue(user.ldap.isLinked) } - func loginNormally() throws -> User { + func loginNormally() async throws -> User { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -170,13 +170,13 @@ class ParseLDAPAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng return nil } } - return try User.login(username: "parse", password: "user") + return try await User.login(username: "parse", password: "user") } @MainActor func testLink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -209,7 +209,7 @@ class ParseLDAPAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng @MainActor func testLinkAuthData() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -244,7 +244,7 @@ class ParseLDAPAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng @MainActor func testUnlink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() let authData = ParseLDAP diff --git a/Tests/ParseSwiftTests/ParseLiveQueryAsyncTests.swift b/Tests/ParseSwiftTests/ParseLiveQueryAsyncTests.swift index a53b451b8..17c8cd044 100644 --- a/Tests/ParseSwiftTests/ParseLiveQueryAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseLiveQueryAsyncTests.swift @@ -49,7 +49,7 @@ class ParseLiveQueryAsyncTests: XCTestCase { // swiftlint:disable:this type_body do { _ = try await client.open(isUserWantsToConnect: true) - XCTFail("Should always fail since WS isn't intercepted.") + XCTFail("Should always fail since WS is not intercepted.") } catch { XCTAssertNotNil(error) } @@ -94,7 +94,7 @@ class ParseLiveQueryAsyncTests: XCTestCase { // swiftlint:disable:this type_body XCTFail("Should have produced error") } catch { XCTAssertEqual(client.isSocketEstablished, true) - XCTAssertNotNil(error) // Should have error because testcases don't intercept websocket + XCTAssertNotNil(error) // Should have error because testcases do not intercept websocket } } } diff --git a/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift b/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift index 8d9de4229..1dcefbba8 100644 --- a/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseLiveQueryCombineTests.swift @@ -57,7 +57,7 @@ class ParseLiveQueryCombineTests: XCTestCase { case .finished: XCTFail("Should have produced failure") case .failure(let error): - XCTAssertNotNil(error) //Should always fail since WS isn't intercepted. + XCTAssertNotNil(error) //Should always fail since WS is not intercepted. } expectation1.fulfill() @@ -124,7 +124,7 @@ class ParseLiveQueryCombineTests: XCTestCase { XCTFail("Should have produced failure") case .failure(let error): XCTAssertEqual(client.isSocketEstablished, true) - XCTAssertNotNil(error) // Should have error because testcases don't intercept websocket + XCTAssertNotNil(error) // Should have error because testcases do not intercept websocket } expectation1.fulfill() diff --git a/Tests/ParseSwiftTests/ParseLiveQueryTests.swift b/Tests/ParseSwiftTests/ParseLiveQueryTests.swift index 29a5e3d1b..536dc4d7e 100644 --- a/Tests/ParseSwiftTests/ParseLiveQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseLiveQueryTests.swift @@ -446,7 +446,7 @@ class ParseLiveQueryTests: XCTestCase { client.close() let expectation1 = XCTestExpectation(description: "Response delegate") client.open(isUserWantsToConnect: true) { error in - XCTAssertNotNil(error) //Should always fail since WS isn't intercepted. + XCTAssertNotNil(error) //Should always fail since WS is not intercepted. expectation1.fulfill() } wait(for: [expectation1], timeout: 20.0) @@ -604,7 +604,7 @@ class ParseLiveQueryTests: XCTestCase { let expectation1 = XCTestExpectation(description: "Send Ping") client.sendPing { error in XCTAssertEqual(client.isSocketEstablished, true) - XCTAssertNotNil(error) // Should have error because testcases don't intercept websocket + XCTAssertNotNil(error) // Should have error because testcases do not intercept websocket expectation1.fulfill() } wait(for: [expectation1], timeout: 20.0) @@ -833,7 +833,7 @@ class ParseLiveQueryTests: XCTestCase { if let socketEstablished = ParseLiveQuery.client?.isSocketEstablished { XCTAssertFalse(socketEstablished) } else { - XCTFail("Should have socket that isn't established") + XCTFail("Should have socket that is not established") expectation2.fulfill() return } diff --git a/Tests/ParseSwiftTests/ParsePushTests.swift b/Tests/ParseSwiftTests/ParsePushTests.swift index ad449fd47..e79481edc 100644 --- a/Tests/ParseSwiftTests/ParsePushTests.swift +++ b/Tests/ParseSwiftTests/ParsePushTests.swift @@ -13,7 +13,7 @@ import XCTest /** For testing _PushStatus response of at least Parse Server 5.2.1 and below. - warning: This struct should be kept inline with `ParsePushStatus` - so tests don't break. + so tests do not break. */ internal struct ParsePushStatusResponse: ParseObject { diff --git a/Tests/ParseSwiftTests/ParseRelationTests.swift b/Tests/ParseSwiftTests/ParseRelationTests.swift index 8969046de..65d8aab72 100644 --- a/Tests/ParseSwiftTests/ParseRelationTests.swift +++ b/Tests/ParseSwiftTests/ParseRelationTests.swift @@ -129,7 +129,7 @@ class ParseRelationTests: XCTestCase { var level = Level(level: 1) level.objectId = "nice" - // Shouldn't produce a relation without an objectId. + // Should not produce a relation without an objectId. XCTAssertThrowsError(try score.relation("yolo", child: level)) let objectId = "hello" @@ -247,7 +247,7 @@ class ParseRelationTests: XCTestCase { level.objectId = "nice" relation.className = level.className - // Shouldn't produce a relation without an objectId. + // Should not produce a relation without an objectId. XCTAssertThrowsError(try relation.add([level])) XCTAssertThrowsError(try relation.add("yolo", objects: [level])) } @@ -366,7 +366,7 @@ class ParseRelationTests: XCTestCase { level.objectId = "nice" relation.className = level.className - // Shouldn't produce a relation without an objectId. + // Should not produce a relation without an objectId. XCTAssertThrowsError(try relation.remove([level])) XCTAssertThrowsError(try relation.remove("yolo", objects: [level])) } @@ -492,7 +492,7 @@ class ParseRelationTests: XCTestCase { level.objectId = "nice" relation.className = level.className - // Shouldn't produce a relation without an objectId. + // Should not produce a relation without an objectId. do { let _: Query = try relation.query() XCTFail("Should have thrown error") diff --git a/Tests/ParseSwiftTests/ParseRoleTests.swift b/Tests/ParseSwiftTests/ParseRoleTests.swift index bfe43142e..e01f1a643 100644 --- a/Tests/ParseSwiftTests/ParseRoleTests.swift +++ b/Tests/ParseSwiftTests/ParseRoleTests.swift @@ -171,7 +171,7 @@ class ParseRoleTests: XCTestCase { acl.publicRead = true var role = try Role(name: "Administrator", acl: acl) - XCTAssertNil(role.users) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.users) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let userRoles = role.users else { XCTFail("Should have unwrapped") @@ -354,7 +354,7 @@ class ParseRoleTests: XCTestCase { acl.publicRead = true var role = try Role(name: "Administrator", acl: acl) - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -385,7 +385,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -427,7 +427,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -456,7 +456,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -499,7 +499,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -527,7 +527,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -579,7 +579,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -613,7 +613,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") @@ -664,7 +664,7 @@ class ParseRoleTests: XCTestCase { var role = try Role(name: "Administrator", acl: acl) role.createdAt = Date() role.updatedAt = Date() - XCTAssertNil(role.roles) // Shouldn't produce a relation without an objectId. + XCTAssertNil(role.roles) // Should not produce a relation without an objectId. role.objectId = "yolo" guard let roles = role.roles else { XCTFail("Should have unwrapped") diff --git a/Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift b/Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift index 119381e22..2c991f1e7 100644 --- a/Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift @@ -157,7 +157,7 @@ class ParseSpotifyAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l XCTAssertTrue(user.spotify.isLinked) } - func loginNormally() throws -> User { + func loginNormally() async throws -> User { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -168,12 +168,12 @@ class ParseSpotifyAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l return nil } } - return try User.login(username: "parse", password: "user") + return try await User.login(username: "parse", password: "user") } @MainActor func testLink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -205,7 +205,7 @@ class ParseSpotifyAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l @MainActor func testLinkAuthData() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -238,7 +238,7 @@ class ParseSpotifyAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l @MainActor func testUnlink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() let authData = ParseSpotify diff --git a/Tests/ParseSwiftTests/ParseTwitterAsyncTests.swift b/Tests/ParseSwiftTests/ParseTwitterAsyncTests.swift index 9394da0ba..924687630 100644 --- a/Tests/ParseSwiftTests/ParseTwitterAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseTwitterAsyncTests.swift @@ -168,7 +168,7 @@ class ParseTwitterAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l XCTAssertTrue(user.twitter.isLinked) } - func loginNormally() throws -> User { + func loginNormally() async throws -> User { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -179,13 +179,13 @@ class ParseTwitterAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l return nil } } - return try User.login(username: "parse", password: "user") + return try await User.login(username: "parse", password: "user") } @MainActor func testLink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -223,7 +223,7 @@ class ParseTwitterAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l @MainActor func testLinkAuthData() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() @@ -263,7 +263,7 @@ class ParseTwitterAsyncTests: XCTestCase { // swiftlint:disable:this type_body_l @MainActor func testUnlink() async throws { - _ = try loginNormally() + _ = try await loginNormally() MockURLProtocol.removeAll() let authData = ParseTwitter diff --git a/Tests/ParseSwiftTests/ParseUserAsyncTests.swift b/Tests/ParseSwiftTests/ParseUserAsyncTests.swift index ce530f02b..db3e314f3 100644 --- a/Tests/ParseSwiftTests/ParseUserAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseUserAsyncTests.swift @@ -179,7 +179,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -223,7 +223,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -262,7 +262,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -336,7 +336,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -375,14 +375,14 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng _ = try await User.logout() if let userFromKeychain = BaseParseUser.current { - XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") + XCTFail("\(userFromKeychain) was not deleted from Keychain during logout") } if let installationFromMemory: CurrentInstallationContainer = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) { if installationFromMemory.installationId == oldInstallationId || installationFromMemory.installationId == nil { - XCTFail("\(installationFromMemory) wasn't deleted and recreated in memory during logout") + XCTFail("\(installationFromMemory) was not deleted and recreated in memory during logout") } } else { XCTFail("Should have a new installation") @@ -393,7 +393,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) { if installationFromKeychain.installationId == oldInstallationId || installationFromKeychain.installationId == nil { - XCTFail("\(installationFromKeychain) wasn't deleted & recreated in Keychain during logout") + XCTFail("\(installationFromKeychain) was not deleted & recreated in Keychain during logout") } } else { XCTFail("Should have a new installation") @@ -435,14 +435,14 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng } if let userFromKeychain = BaseParseUser.current { - XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") + XCTFail("\(userFromKeychain) was not deleted from Keychain during logout") } if let installationFromMemory: CurrentInstallationContainer = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) { if installationFromMemory.installationId == oldInstallationId || installationFromMemory.installationId == nil { - XCTFail("\(installationFromMemory) wasn't deleted & recreated in memory during logout") + XCTFail("\(installationFromMemory) was not deleted & recreated in memory during logout") } } else { XCTFail("Should have a new installation") @@ -453,7 +453,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) { if installationFromKeychain.installationId == oldInstallationId || installationFromKeychain.installationId == nil { - XCTFail("\(installationFromKeychain) wasn't deleted & recreated in Keychain during logout") + XCTFail("\(installationFromKeychain) was not deleted & recreated in Keychain during logout") } } else { XCTFail("Should have a new installation") @@ -532,7 +532,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertNil(currentUser.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -576,7 +576,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertNil(currentUser.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -616,7 +616,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertNil(currentUser.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -729,7 +729,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(fetched.objectId, serverResponse.objectId) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -764,7 +764,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(saved.objectId, serverResponse.objectId) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -1100,7 +1100,7 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng _ = try await user.delete() if BaseParseUser.current != nil { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") } } diff --git a/Tests/ParseSwiftTests/ParseUserCombineTests.swift b/Tests/ParseSwiftTests/ParseUserCombineTests.swift index 67114111e..b3f4339a2 100644 --- a/Tests/ParseSwiftTests/ParseUserCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseUserCombineTests.swift @@ -126,7 +126,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -182,7 +182,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -233,7 +233,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -317,7 +317,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -364,14 +364,14 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTFail(error.localizedDescription) } if let userFromKeychain = BaseParseUser.current { - XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") + XCTFail("\(userFromKeychain) was not deleted from Keychain during logout") } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if let installationFromMemory: CurrentInstallationContainer = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) { if installationFromMemory.installationId == oldInstallationId || installationFromMemory.installationId == nil { - XCTFail("\(installationFromMemory) wasn't deleted and recreated in memory during logout") + XCTFail("\(installationFromMemory) was not deleted and recreated in memory during logout") } } else { XCTFail("Should have a new installation") @@ -382,7 +382,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) { if installationFromKeychain.installationId == oldInstallationId || installationFromKeychain.installationId == nil { - XCTFail("\(installationFromKeychain) wasn't deleted & recreated in Keychain during logout") + XCTFail("\(installationFromKeychain) was not deleted & recreated in Keychain during logout") } } else { XCTFail("Should have a new installation") @@ -427,14 +427,14 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le } if let userFromKeychain = BaseParseUser.current { - XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") + XCTFail("\(userFromKeychain) was not deleted from Keychain during logout") } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if let installationFromMemory: CurrentInstallationContainer = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) { if installationFromMemory.installationId == oldInstallationId || installationFromMemory.installationId == nil { - XCTFail("\(installationFromMemory) wasn't deleted & recreated in memory during logout") + XCTFail("\(installationFromMemory) was not deleted & recreated in memory during logout") } } else { XCTFail("Should have a new installation") @@ -446,7 +446,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le if installationFromKeychain.installationId == oldInstallationId || installationFromKeychain.installationId == nil { // swiftlint:disable:next line_length - XCTFail("\(installationFromKeychain) wasn't deleted & recreated in Keychain during logout") + XCTFail("\(installationFromKeychain) was not deleted & recreated in Keychain during logout") } } else { XCTFail("Should have a new installation") @@ -556,7 +556,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTAssertNil(currentUser.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -701,7 +701,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTAssertEqual(fetched.objectId, serverResponse.objectId) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -751,7 +751,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le XCTAssertEqual(saved.objectId, serverResponse.objectId) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -879,7 +879,7 @@ class ParseUserCombineTests: XCTestCase { // swiftlint:disable:this type_body_le }, receiveValue: { _ in if BaseParseUser.current != nil { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") } }) publisher.store(in: &subscriptions) diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index 5c16c298d..3fca61e97 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -1327,7 +1327,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -1375,7 +1375,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -1412,7 +1412,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") expectation1.fulfill() return } @@ -1470,7 +1470,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(signedUp.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") expectation1.fulfill() return } @@ -1540,7 +1540,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(loggedIn.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") return } @@ -1578,7 +1578,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(loggedIn.ACL) guard let userFromKeychain = BaseParseUser.current else { - XCTFail("Couldn't get CurrentUser from Keychain") + XCTFail("Could not get CurrentUser from Keychain") expectation1.fulfill() return } @@ -1645,7 +1645,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length do { try User.logout() if let userFromKeychain = BaseParseUser.current { - XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") + XCTFail("\(userFromKeychain) was not deleted from Keychain during logout") } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { @@ -1653,7 +1653,10 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length if installationFromKeychain.installationId == oldInstallationId || installationFromKeychain.installationId == nil { - XCTFail("\(installationFromKeychain) wasn't deleted then created in Keychain during logout") + XCTFail(""" + "\(installationFromKeychain) was not deleted then created in + Keychain during logout + """) } } else { @@ -1684,7 +1687,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length case .success: if let userFromKeychain = BaseParseUser.current { - XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") + XCTFail("\(userFromKeychain) was not deleted from Keychain during logout") } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if let installationFromMemory: CurrentInstallationContainer @@ -1692,7 +1695,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length if installationFromMemory.installationId == oldInstallationId || installationFromMemory.installationId == nil { - XCTFail("\(installationFromMemory) wasn't deleted & recreated in memory during logout") + XCTFail("\(installationFromMemory) was not deleted & recreated in memory during logout") } } else { XCTFail("Should have a new installation") @@ -1704,7 +1707,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length if installationFromKeychain.installationId == oldInstallationId || installationFromKeychain.installationId == nil { // swiftlint:disable:next line_length - XCTFail("\(installationFromKeychain) wasn't deleted & recreated in Keychain during logout") + XCTFail("\(installationFromKeychain) was not deleted & recreated in Keychain during logout") } } else { XCTFail("Should have a new installation")