./.codecov.yml Gemfile Gemfile.lock LICENSE Package.swift Parse.xcworkspace/contents.xcworkspacedata Parse.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ParseSwift-iOS/Info.plist ParseSwift-macOS/Info.plist ParseSwift-tvOS/Info.plist ParseSwift-tvOS/ParseSwift_tvOS.h ParseSwift-watchOS/Info.plist ParseSwift-watchOS/ParseSwift_watchOS.h ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift ParseSwift.playground/Pages/3 - User - Sign Up.xcplaygroundpage/Contents.swift ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift ParseSwift.playground/Pages/5 - ACL.xcplaygroundpage/Contents.swift ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift ParseSwift.playground/Sources/Common.swift ParseSwift.playground/contents.xcplayground ParseSwift.podspec ParseSwift.xcodeproj/project.pbxproj ParseSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata ParseSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (iOS).xcscheme ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (macOS).xcscheme ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (tvOS).xcscheme Scripts/jazzy.sh Sources/ParseSwift/API/API+Commands.swift Sources/ParseSwift/API/API.swift Sources/ParseSwift/API/BatchUtils.swift Sources/ParseSwift/API/Responses.swift Sources/ParseSwift/API/URLSession+extensions.swift Sources/ParseSwift/Coding/AnyCodable.swift Sources/ParseSwift/Coding/AnyDecodable.swift Sources/ParseSwift/Coding/AnyEncodable.swift Sources/ParseSwift/Coding/Extensions.swift Sources/ParseSwift/Coding/ParseCoding.swift Sources/ParseSwift/Coding/ParseEncoder.swift Sources/ParseSwift/Mutation Operations/AddOperation.swift Sources/ParseSwift/Mutation Operations/AddUniqueOperation.swift Sources/ParseSwift/Mutation Operations/DeleteOperation.swift Sources/ParseSwift/Mutation Operations/IncrementOperation.swift Sources/ParseSwift/Mutation Operations/ParseMutationContainer.swift Sources/ParseSwift/Mutation Operations/RemoveOperation.swift Sources/ParseSwift/Objects/ParseInstallation.swift Sources/ParseSwift/Objects/ParseObject.swift Sources/ParseSwift/Objects/ParseUser.swift Sources/ParseSwift/Objects/Protocols/Fetchable.swift Sources/ParseSwift/Objects/Protocols/Queryable.swift Sources/ParseSwift/Objects/Protocols/Saveable.swift Sources/ParseSwift/Parse Types/ACL.swift Sources/ParseSwift/Parse Types/File.swift Sources/ParseSwift/Parse Types/GeoPoint.swift Sources/ParseSwift/Parse Types/Internal/BaseParseInstallation.swift Sources/ParseSwift/Parse Types/Internal/BaseParseUser.swift Sources/ParseSwift/Parse Types/Internal/FindResult.swift Sources/ParseSwift/Parse Types/Internal/NoBody.swift Sources/ParseSwift/Parse Types/ParseError.swift Sources/ParseSwift/Parse Types/Pointer.swift Sources/ParseSwift/Parse Types/Query.swift Sources/ParseSwift/Parse.h Sources/ParseSwift/Parse.swift Sources/ParseSwift/ParseConstants.swift Sources/ParseSwift/Storage/KeychainStore.swift Sources/ParseSwift/Storage/ParseStorage.swift Sources/ParseSwift/Storage/PrimitiveObjectStore.swift Sources/ParseSwift/Storage/SecureStorage.swift TestHost/AppDelegate.swift TestHost/Assets.xcassets/AppIcon.appiconset/Contents.json TestHost/Base.lproj/LaunchScreen.storyboard TestHost/Base.lproj/Main.storyboard TestHost/Info.plist Tests/LinuxMain.swift Tests/ParseSwiftTests/ACLTests.swift Tests/ParseSwiftTests/APICommandTests.swift Tests/ParseSwiftTests/AnyCodableTests/AnyCodableTests.swift Tests/ParseSwiftTests/AnyCodableTests/AnyDecodableTests.swift Tests/ParseSwiftTests/AnyCodableTests/AnyEncodableTests.swift Tests/ParseSwiftTests/Info.plist Tests/ParseSwiftTests/KeychainStoreTests.swift Tests/ParseSwiftTests/NetworkMocking/MockURLProtocol.swift Tests/ParseSwiftTests/NetworkMocking/MockURLResponse.swift Tests/ParseSwiftTests/ParseEncoderTests.swift Tests/ParseSwiftTests/ParseInstallationTests.swift Tests/ParseSwiftTests/ParseObjectBatchTests.swift Tests/ParseSwiftTests/ParseObjectCommandTests.swift Tests/ParseSwiftTests/ParseQueryTests.swift Tests/ParseSwiftTests/ParseUserCommandTests.swift <<<<<< network # path=./ParseSwift.framework.coverage.txt /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/API/API+Commands.swift: 1| |// 2| |// API+Commands.swift 3| |// ParseSwift (iOS) 4| |// 5| |// Created by Florent Vilmart on 17-09-24. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |internal extension API { 12| | 13| | struct Command: Encodable where T: Encodable { 14| | typealias ReturnType = U // swiftlint:disable:this nesting 15| | let method: API.Method 16| | let path: API.Endpoint 17| | let body: T? 18| | let mapper: ((Data) throws -> U) 19| | let params: [String: String?]? 20| | 21| | init(method: API.Method, 22| | path: API.Endpoint, 23| | params: [String: String]? = nil, 24| | body: T? = nil, 25| 3.44k| mapper: @escaping ((Data) throws -> U)) { 26| 3.44k| self.method = method 27| 3.44k| self.path = path 28| 3.44k| self.body = body 29| 3.44k| self.mapper = mapper 30| 3.44k| self.params = params 31| 3.44k| } 32| | 33| 57| public func execute(options: API.Options) throws -> U { 34| 57| var responseResult: Result? 35| 57| 36| 57| let group = DispatchGroup() 37| 57| group.enter() 38| 57| self.executeAsync(options: options, callbackQueue: nil) { result in 39| 57| responseResult = result 40| 57| group.leave() 41| 57| } 42| 57| group.wait() 43| 57| 44| 57| guard let response = responseResult else { 45| 0| throw ParseError(code: .unknownError, 46| 0| message: "couldn't unrwrap server response") 47| 57| } 48| 57| return try response.get() 49| 57| } 50| | 51| | public func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, 52| 1.77k| completion: @escaping(Result) -> Void) { 53| 1.77k| let params = self.params?.getQueryItems() 54| 1.77k| let headers = API.getHeaders(options: options) 55| 1.77k| let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent) 56| 1.77k| 57| 1.77k| guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { 58| 0| completion(.failure(ParseError(code: .unknownError, 59| 0| message: "couldn't unrwrap url components for \(url)"))) 60| 0| return 61| 1.77k| } 62| 1.77k| components.queryItems = params 63| 1.77k| 64| 1.77k| guard let urlComponents = components.url else { 65| 0| completion(.failure(ParseError(code: .unknownError, 66| 0| message: "couldn't create url from components for \(components)"))) 67| 0| return 68| 1.77k| } 69| 1.77k| 70| 1.77k| var urlRequest = URLRequest(url: urlComponents) 71| 1.77k| urlRequest.allHTTPHeaderFields = headers 72| 1.77k| if let urlBody = body { 73| 1.34k| guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { 74| 0| completion(.failure(ParseError(code: .unknownError, 75| 0| message: "couldn't encode body \(urlBody)"))) 76| 0| return 77| 1.34k| } 78| 1.34k| urlRequest.httpBody = bodyData 79| 1.77k| } 80| 1.77k| urlRequest.httpMethod = method.rawValue 81| 1.77k| 82| 1.77k| URLSession.shared.dataTask(with: urlRequest, callbackQueue: callbackQueue, mapper: mapper) { result in 83| 1.77k| switch result { 84| 1.77k| 85| 1.77k| case .success(let decoded): 86| 1.77k| completion(.success(decoded)) 87| 1.77k| 88| 1.77k| case .failure(let error): 89| 4| completion(.failure(error)) 90| 1.77k| } 91| 1.77k| } 92| 1.77k| } 93| | 94| | enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting 95| | case method, body, path 96| | } 97| | } 98| |} 99| | 100| |internal extension API.Command { 101| | // MARK: Saving 102| 1.45k| static func saveCommand(_ object: T) -> API.Command where T: ParseObject { 103| 1.45k| if object.isSaved { 104| 832| return updateCommand(object) 105| 832| } 106| 620| return createCommand(object) 107| 1.45k| } 108| | 109| | // MARK: Saving - private 110| 618| private static func createCommand(_ object: T) -> API.Command where T: ParseObject { 111| 618| let mapper = { (data) -> T in 112| 204| try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: object) 113| 204| } 114| 618| return API.Command(method: .POST, 115| 618| path: object.endpoint, 116| 618| body: object, 117| 618| mapper: mapper) 118| 618| } 119| | 120| 832| private static func updateCommand(_ object: T) -> API.Command where T: ParseObject { 121| 832| let mapper = { (data: Data) -> T in 122| 416| try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: object) 123| 416| } 124| 832| return API.Command(method: .PUT, 125| 832| path: object.endpoint, 126| 832| body: object, 127| 832| mapper: mapper) 128| 832| } 129| | 130| | // MARK: Fetching 131| 412| static func fetchCommand(_ object: T) throws -> API.Command where T: ParseObject { 132| 412| guard object.isSaved else { 133| 0| throw ParseError(code: .unknownError, message: "Cannot Fetch an object without id") 134| 412| } 135| 412| 136| 412| return API.Command( 137| 412| method: .GET, 138| 412| path: object.endpoint 139| 412| ) { (data) -> T in 140| 410| try ParseCoding.jsonDecoder().decode(FetchResponse.self, from: data).apply(to: object) 141| 410| } 142| 412| } 143| |} 144| | 145| |extension API.Command where T: ParseObject { 146| | 147| 6| internal var data: Data? { 148| 6| guard let body = body else { return nil } 149| 4| return try? body.getEncoder().encode(body) 150| 6| } 151| | 152| 414| static func batch(commands: [API.Command]) -> RESTBatchCommandType { 153| 827| let commands = commands.compactMap { (command) -> API.Command? in 154| 827| let path = ParseConfiguration.mountPath + command.path.urlComponent 155| 827| guard let body = command.body else { 156| 0| return nil 157| 827| } 158| 827| return API.Command(method: command.method, path: .any(path), 159| 827| body: body, mapper: command.mapper) 160| 827| } 161| 828| let bodies = commands.compactMap { (command) -> (body: T, command: API.Method)? in 162| 828| guard let body = command.body else { 163| 0| return nil 164| 828| } 165| 828| return (body: body, command: command.method) 166| 828| } 167| 414| let mapper = { (data: Data) -> [Result] in 168| 414| let decodingType = [BatchResponseItem].self 169| 414| do { 170| 414| let responses = try ParseCoding.jsonDecoder().decode(decodingType, from: data) 171| 828| return bodies.enumerated().map({ (object) -> (Result) in 172| 828| let response = responses[object.offset] 173| 828| if let success = response.success { 174| 820| return .success(success.apply(to: object.element.body, method: object.element.command)) 175| 820| } else { 176| 8| guard let parseError = response.error else { 177| 8| return .failure(ParseError(code: .unknownError, message: "unknown error")) 178| 8| } 179| 0| 180| 0| return .failure(parseError) 181| 8| } 182| 0| }) 183| 414| } catch { 184| 0| guard let parseError = error as? ParseError else { 185| 0| return [(.failure(ParseError(code: .unknownError, message: "decoding error: \(error)")))] 186| 0| } 187| 0| return [(.failure(parseError))] 188| 0| } 189| 0| } 190| 414| let batchCommand = BatchCommand(requests: commands) 191| 414| return RESTBatchCommandType(method: .POST, path: .batch, body: batchCommand, mapper: mapper) 192| 414| } 193| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/API/API.swift: 1| |// 2| |// API.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-08-19. 6| |// Copyright © 2017 Parse. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |public struct API { 12| | 13| | internal enum Method: String, Encodable { 14| | case GET, POST, PUT, DELETE 15| | } 16| | 17| | internal enum Endpoint: Encodable { 18| | case batch 19| | case objects(className: String) 20| | case object(className: String, objectId: String) 21| | case login 22| | case signup 23| | case logout 24| | case any(String) 25| | 26| 3.44k| var urlComponent: String { 27| 3.44k| switch self { 28| 3.44k| case .batch: 29| 414| return "/batch" 30| 3.44k| case .objects(let className): 31| 926| return "/classes/\(className)" 32| 3.44k| case .object(let className, let objectId): 33| 1.24k| return "/classes/\(className)/\(objectId)" 34| 3.44k| case .login: 35| 23| return "/login" 36| 3.44k| case .signup: 37| 4| return "/users" 38| 3.44k| case .logout: 39| 2| return "/users/logout" 40| 3.44k| case .any(let path): 41| 825| return path 42| 3.44k| } 43| 3.44k| } 44| | 45| 828| public func encode(to encoder: Encoder) throws { 46| 828| var container = encoder.singleValueContainer() 47| 828| try container.encode(urlComponent) 48| 828| } 49| | } 50| | 51| | public typealias Options = Set 52| | 53| | public enum Option: Hashable { 54| | case useMasterKey 55| | case sessionToken(String) 56| | case installationId(String) 57| | 58| | // use HashValue so we can use in a sets 59| 719| public func hash(into hasher: inout Hasher) { 60| 719| switch self { 61| 719| case .useMasterKey: 62| 617| hasher.combine(1) 63| 719| case .sessionToken: 64| 100| hasher.combine(2) 65| 719| case .installationId: 66| 1| hasher.combine(3) 67| 719| } 68| 719| } 69| | } 70| | 71| 1.77k| internal static func getHeaders(options: API.Options) -> [String: String] { 72| 1.77k| var headers: [String: String] = ["X-Parse-Application-Id": ParseConfiguration.applicationId, 73| 1.77k| "Content-Type": "application/json"] 74| 1.77k| if let clientKey = ParseConfiguration.clientKey { 75| 1.77k| headers["X-Parse-Client-Key"] = clientKey 76| 1.77k| } 77| 1.77k| 78| 1.77k| if let token = BaseParseUser.currentUserContainer?.sessionToken { 79| 12| headers["X-Parse-Session-Token"] = token 80| 1.77k| } 81| 1.77k| 82| 1.77k| if let installationId = BaseParseInstallation.currentInstallationContainer.installationId { 83| 1.77k| headers["X-Parse-Installation-Id"] = installationId 84| 1.77k| } 85| 1.77k| 86| 1.77k| options.forEach { (option) in 87| 718| switch option { 88| 718| case .useMasterKey: 89| 618| headers["X-Parse-Master-Key"] = ParseConfiguration.masterKey 90| 718| case .sessionToken(let sessionToken): 91| 100| headers["X-Parse-Session-Token"] = sessionToken 92| 718| case .installationId(let installationId): 93| 1| headers["X-Parse-Installation-Id"] = installationId 94| 718| } 95| 718| } 96| 1.77k| 97| 1.77k| return headers 98| 1.77k| } 99| |} 100| | 101| |internal extension Dictionary where Key == String, Value == String? { 102| 18| func getQueryItems() -> [URLQueryItem] { 103| 36| return map { (key, value) -> URLQueryItem in 104| 36| return URLQueryItem(name: key, value: value) 105| 36| } 106| 18| } 107| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/API/BatchUtils.swift: 1| |// 2| |// RESTBatchCommand.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-08-19. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |typealias ParseObjectBatchCommand = BatchCommand where T: ParseObject 12| |typealias ParseObjectBatchResponse = [(Result)] 13| |// swiftlint:disable line_length 14| |typealias RESTBatchCommandType = API.Command, ParseObjectBatchResponse> where T: ParseObject 15| |// swiftlint:enable line_length 16| | 17| |internal struct BatchCommand: Encodable where T: Encodable { 18| | let requests: [API.Command] 19| |} 20| | 21| |internal struct BatchResponseItem: Codable where T: Codable { 22| | let success: T? 23| | let error: ParseError? 24| |} 25| | 26| |internal struct WriteResponse: Codable { 27| | var objectId: String? 28| | var createdAt: Date? 29| | var updatedAt: Date? 30| | 31| 410| func asSaveResponse() -> SaveResponse { 32| 410| guard let objectId = objectId, let createdAt = createdAt else { 33| 0| fatalError("Cannot create a SaveResponse without objectId") 34| 410| } 35| 410| return SaveResponse(objectId: objectId, createdAt: createdAt) 36| 410| } 37| | 38| 410| func asUpdateResponse() -> UpdateResponse { 39| 410| guard let updatedAt = updatedAt else { 40| 0| fatalError("Cannot create an UpdateResponse without updatedAt") 41| 410| } 42| 410| return UpdateResponse(updatedAt: updatedAt) 43| 410| } 44| | 45| 0| func asFetchResponse() -> FetchResponse { 46| 0| guard let createdAt = createdAt, let updatedAt = updatedAt else { 47| 0| fatalError("Cannot create a SaveResponse without objectId") 48| 0| } 49| 0| return FetchResponse(createdAt: createdAt, updatedAt: updatedAt) 50| 0| } 51| | 52| 820| func apply(to object: T, method: API.Method) -> T where T: ParseObject { 53| 820| switch method { 54| 820| case .POST: 55| 410| return asSaveResponse().apply(to: object) 56| 820| case .PUT: 57| 410| return asUpdateResponse().apply(to: object) 58| 820| case .GET: 59| 0| return asFetchResponse().apply(to: object) 60| 820| default: 61| 0| fatalError("There is no configured way to apply for method: \(method)") 62| 820| } 63| 820| } 64| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/API/Responses.swift: 1| |// 2| |// Responses.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-08-20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |internal struct SaveResponse: Decodable { 12| | var objectId: String 13| | var createdAt: Date 14| 614| var updatedAt: Date { 15| 614| return createdAt 16| 614| } 17| | 18| 614| func apply(to object: T) -> T where T: ParseObject { 19| 614| var object = object 20| 614| object.objectId = objectId 21| 614| object.createdAt = createdAt 22| 614| object.updatedAt = updatedAt 23| 614| return object 24| 614| } 25| |} 26| | 27| |internal struct UpdateResponse: Decodable { 28| | var updatedAt: Date 29| | 30| 826| func apply(to object: T) -> T where T: ParseObject { 31| 826| var object = object 32| 826| object.updatedAt = updatedAt 33| 826| return object 34| 826| } 35| |} 36| | 37| |internal struct FetchResponse: Decodable { 38| | var createdAt: Date 39| | var updatedAt: Date 40| | 41| 410| func apply(to object: T) -> T where T: ParseObject { 42| 410| var object = object 43| 410| object.createdAt = createdAt 44| 410| object.updatedAt = updatedAt 45| 410| return object 46| 410| } 47| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/API/URLSession+extensions.swift: 1| |// 2| |// URLSession+extensions.swift 3| |// ParseSwift 4| |// 5| |// Original file, URLSession+sync.swift, created by Florent Vilmart on 17-09-24. 6| |// Name change to URLSession+extensions.swift and support for sync/async by Corey Baker on 7/25/20. 7| |// Copyright © 2020 Parse Community. All rights reserved. 8| |// 9| | 10| |import Foundation 11| | 12| |extension URLSession { 13| | 14| | internal func dataTask( 15| | with request: URLRequest, 16| | callbackQueue: DispatchQueue?, 17| | mapper: @escaping (Data) throws -> U, 18| | completion: @escaping(Result) -> Void 19| 1.77k| ) { 20| 1.77k| func makeResult(responseData: Data?, urlResponse: URLResponse?, 21| 1.77k| responseError: Error?) -> Result { 22| 1.77k| if let responseData = responseData { 23| 1.77k| do { 24| 1.77k| return try .success(mapper(responseData)) 25| 1.77k| } catch { 26| 2| let parseError = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: responseData) 27| 2| return .failure(parseError ?? ParseError(code: .unknownError, message: "cannot decode error")) 28| 18.4E| } 29| 18.4E| } else if let responseError = responseError { 30| 2| return .failure(ParseError(code: .unknownError, message: "Unable to sync: \(responseError)")) 31| 18.4E| } else { 32| 18.4E| return .failure(ParseError(code: .unknownError, 33| 18.4E| message: "Unable to sync: \(String(describing: urlResponse)).")) 34| 18.4E| } 35| 0| } 36| 1.77k| 37| 1.77k| dataTask(with: request) { (responseData, urlResponse, responseError) in 38| 1.77k| let result = makeResult(responseData: responseData, urlResponse: urlResponse, responseError: responseError) 39| 1.77k| 40| 1.77k| if let callbackQueue = callbackQueue { 41| 1.72k| callbackQueue.async { completion(result) } 42| 1.77k| } else { 43| 57| completion(result) 44| 1.77k| } 45| 1.77k| }.resume() 46| 1.77k| } 47| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Coding/AnyCodable.swift: 1| |import Foundation 2| | 3| |/** 4| | A type-erased `Codable` value. 5| | 6| | The `AnyCodable` type forwards encoding and decoding responsibilities 7| | to an underlying value, hiding its specific underlying type. 8| | 9| | You can encode or decode mixed-type values in dictionaries 10| | and other collections that require `Encodable` or `Decodable` conformance 11| | by declaring their contained type to be `AnyCodable`. 12| | 13| | - SeeAlso: `AnyEncodable` 14| | - SeeAlso: `AnyDecodable` 15| | 16| | Source: https://github.com/Flight-School/AnyCodable 17| | */ 18| |public struct AnyCodable: Codable { 19| | public typealias DateEncodingStrategy = (Date, Encoder) throws -> Void 20| | 21| | public let dateEncodingStrategy: DateEncodingStrategy? 22| | public let value: Any 23| | 24| 33| public init(_ value: T?) { 25| 33| self.dateEncodingStrategy = nil 26| 33| self.value = value ?? () 27| 33| } 28| | 29| 3.14k| public init(_ value: T?, dateEncodingStrategy: DateEncodingStrategy?) { 30| 3.14k| self.dateEncodingStrategy = dateEncodingStrategy 31| 3.14k| self.value = value ?? () 32| 3.14k| } 33| |} 34| | 35| |extension AnyCodable: _AnyEncodable, _AnyDecodable {} 36| | 37| |extension AnyCodable: Equatable { 38| 1| public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { // swiftlint:disable:this cyclomatic_complexity line_length 39| 1| switch (lhs.value, rhs.value) { 40| 1| case is (Void, Void): 41| 0| return true 42| 1| case let (lhs as Bool, rhs as Bool): 43| 0| return lhs == rhs 44| 1| case let (lhs as Int, rhs as Int): 45| 0| return lhs == rhs 46| 1| case let (lhs as Int8, rhs as Int8): 47| 0| return lhs == rhs 48| 1| case let (lhs as Int16, rhs as Int16): 49| 0| return lhs == rhs 50| 1| case let (lhs as Int32, rhs as Int32): 51| 0| return lhs == rhs 52| 1| case let (lhs as Int64, rhs as Int64): 53| 0| return lhs == rhs 54| 1| case let (lhs as UInt, rhs as UInt): 55| 0| return lhs == rhs 56| 1| case let (lhs as UInt8, rhs as UInt8): 57| 0| return lhs == rhs 58| 1| case let (lhs as UInt16, rhs as UInt16): 59| 0| return lhs == rhs 60| 1| case let (lhs as UInt32, rhs as UInt32): 61| 0| return lhs == rhs 62| 1| case let (lhs as UInt64, rhs as UInt64): 63| 0| return lhs == rhs 64| 1| case let (lhs as Float, rhs as Float): 65| 0| return lhs == rhs 66| 1| case let (lhs as Double, rhs as Double): 67| 0| return lhs == rhs 68| 1| case let (lhs as String, rhs as String): 69| 1| return lhs == rhs 70| 1| case (let lhs as [String: AnyCodable], let rhs as [String: AnyCodable]): 71| 0| return lhs == rhs 72| 1| case (let lhs as [AnyCodable], let rhs as [AnyCodable]): 73| 0| return lhs == rhs 74| 1| default: 75| 0| return false 76| 1| } 77| 1| } 78| |} 79| | 80| |extension AnyCodable: CustomStringConvertible { 81| 0| public var description: String { 82| 0| switch value { 83| 0| case is Void: 84| 0| return String(describing: nil as Any?) 85| 0| case let value as CustomStringConvertible: 86| 0| return value.description 87| 0| default: 88| 0| return String(describing: value) 89| 0| } 90| 0| } 91| |} 92| | 93| |extension AnyCodable: CustomDebugStringConvertible { 94| 0| public var debugDescription: String { 95| 0| switch value { 96| 0| case let value as CustomDebugStringConvertible: 97| 0| return "AnyCodable(\(value.debugDescription))" 98| 0| default: 99| 0| return "AnyCodable(\(self.description))" 100| 0| } 101| 0| } 102| |} 103| | 104| |extension AnyCodable: ExpressibleByNilLiteral {} 105| |extension AnyCodable: ExpressibleByBooleanLiteral {} 106| |extension AnyCodable: ExpressibleByIntegerLiteral {} 107| |extension AnyCodable: ExpressibleByFloatLiteral {} 108| |extension AnyCodable: ExpressibleByStringLiteral {} 109| |extension AnyCodable: ExpressibleByArrayLiteral {} 110| |extension AnyCodable: ExpressibleByDictionaryLiteral {} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Coding/AnyDecodable.swift: 1| |import Foundation 2| | 3| |/** 4| | A type-erased `Decodable` value. 5| | 6| | The `AnyDecodable` type forwards decoding responsibilities 7| | to an underlying value, hiding its specific underlying type. 8| | 9| | You can decode mixed-type values in dictionaries 10| | and other collections that require `Decodable` conformance 11| | by declaring their contained type to be `AnyDecodable`: 12| | 13| | let json = """ 14| | { 15| | "boolean": true, 16| | "integer": 1, 17| | "double": 3.14159265358979323846, 18| | "string": "string", 19| | "array": [1, 2, 3], 20| | "nested": { 21| | "a": "alpha", 22| | "b": "bravo", 23| | "c": "charlie" 24| | } 25| | } 26| | """.data(using: .utf8)! 27| | 28| | let decoder = JSONDecoder() 29| | let dictionary = try! decoder.decode([String: AnyCodable].self, from: json) 30| | */ 31| |public struct AnyDecodable: Decodable { 32| | public let value: Any 33| 6| public init(_ value: T?) { 34| 6| self.value = value ?? () 35| 6| } 36| |} 37| | 38| |protocol _AnyDecodable { 39| | var value: Any { get } 40| | init(_ value: T?) 41| |} 42| | 43| |extension AnyDecodable: _AnyDecodable {} 44| | 45| |extension _AnyDecodable { 46| 29| public init(from decoder: Decoder) throws { 47| 29| let container = try decoder.singleValueContainer() 48| 29| 49| 29| if container.decodeNil() { 50| 0| self.init(()) 51| 29| } else if let dictionary = try? container.decode([String: AnyCodable].self) { 52| 7| self.init(dictionary.mapValues { $0.value }) 53| 29| } else if let array = try? container.decode([AnyCodable].self) { 54| 6| self.init(array.map { $0.value }) 55| 29| } else if let string = try? container.decode(String.self) { 56| 11| self.init(string) 57| 29| } else if let int = try? container.decode(Int.self) { 58| 9| self.init(int) 59| 29| } else if let uint = try? container.decode(UInt.self) { 60| 0| self.init(uint) 61| 29| } else if let double = try? container.decode(Double.self) { 62| 2| self.init(double) 63| 29| } else if let bool = try? container.decode(Bool.self) { 64| 2| self.init(bool) 65| 29| } else { 66| 27| throw DecodingError.dataCorruptedError(in: container, 67| 27| debugDescription: "AnyCodable value cannot be decoded") 68| 27| } 69| 2| } 70| |} 71| | 72| |extension AnyDecodable: Equatable { 73| 0| public static func == (lhs: AnyDecodable, rhs: AnyDecodable) -> Bool { // swiftlint:disable:this cyclomatic_complexity line_length 74| 0| switch (lhs.value, rhs.value) { 75| 0| case is (Void, Void): 76| 0| return true 77| 0| case let (lhs as Bool, rhs as Bool): 78| 0| return lhs == rhs 79| 0| case let (lhs as Int, rhs as Int): 80| 0| return lhs == rhs 81| 0| case let (lhs as Int8, rhs as Int8): 82| 0| return lhs == rhs 83| 0| case let (lhs as Int16, rhs as Int16): 84| 0| return lhs == rhs 85| 0| case let (lhs as Int32, rhs as Int32): 86| 0| return lhs == rhs 87| 0| case let (lhs as Int64, rhs as Int64): 88| 0| return lhs == rhs 89| 0| case let (lhs as UInt, rhs as UInt): 90| 0| return lhs == rhs 91| 0| case let (lhs as UInt8, rhs as UInt8): 92| 0| return lhs == rhs 93| 0| case let (lhs as UInt16, rhs as UInt16): 94| 0| return lhs == rhs 95| 0| case let (lhs as UInt32, rhs as UInt32): 96| 0| return lhs == rhs 97| 0| case let (lhs as UInt64, rhs as UInt64): 98| 0| return lhs == rhs 99| 0| case let (lhs as Float, rhs as Float): 100| 0| return lhs == rhs 101| 0| case let (lhs as Double, rhs as Double): 102| 0| return lhs == rhs 103| 0| case let (lhs as String, rhs as String): 104| 0| return lhs == rhs 105| 0| case (let lhs as [String: AnyDecodable], let rhs as [String: AnyDecodable]): 106| 0| return lhs == rhs 107| 0| case (let lhs as [AnyDecodable], let rhs as [AnyDecodable]): 108| 0| return lhs == rhs 109| 0| default: 110| 0| return false 111| 0| } 112| 0| } 113| |} 114| | 115| |extension AnyDecodable: CustomStringConvertible { 116| 0| public var description: String { 117| 0| switch value { 118| 0| case is Void: 119| 0| return String(describing: nil as Any?) 120| 0| case let value as CustomStringConvertible: 121| 0| return value.description 122| 0| default: 123| 0| return String(describing: value) 124| 0| } 125| 0| } 126| |} 127| | 128| |extension AnyDecodable: CustomDebugStringConvertible { 129| 0| public var debugDescription: String { 130| 0| switch value { 131| 0| case let value as CustomDebugStringConvertible: 132| 0| return "AnyDecodable(\(value.debugDescription))" 133| 0| default: 134| 0| return "AnyDecodable(\(self.description))" 135| 0| } 136| 0| } 137| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Coding/AnyEncodable.swift: 1| |import Foundation 2| | 3| |/** 4| | A type-erased `Encodable` value. 5| | 6| | The `AnyEncodable` type forwards encoding responsibilities 7| | to an underlying value, hiding its specific underlying type. 8| | 9| | You can encode mixed-type values in dictionaries 10| | and other collections that require `Encodable` conformance 11| | by declaring their contained type to be `AnyEncodable`: 12| | 13| | let dictionary: [String: AnyEncodable] = [ 14| | "boolean": true, 15| | "integer": 1, 16| | "double": 3.14159265358979323846, 17| | "string": "string", 18| | "array": [1, 2, 3], 19| | "nested": [ 20| | "a": "alpha", 21| | "b": "bravo", 22| | "c": "charlie" 23| | ] 24| | ] 25| | 26| | let encoder = JSONEncoder() 27| | let json = try! encoder.encode(dictionary) 28| | 29| | Source: https://github.com/Flight-School/AnyCodable 30| | */ 31| |public struct AnyEncodable: Encodable { 32| | public let dateEncodingStrategy: AnyCodable.DateEncodingStrategy? 33| | public let value: Any 34| | 35| 0| public init(_ value: T?, dateEncodingStrategy: AnyCodable.DateEncodingStrategy?) { 36| 0| self.dateEncodingStrategy = dateEncodingStrategy 37| 0| self.value = value ?? () 38| 0| } 39| | 40| 6| public init(_ value: T?) { 41| 6| self.dateEncodingStrategy = nil 42| 6| self.value = value ?? () 43| 6| } 44| |} 45| | 46| |@usableFromInline 47| |protocol _AnyEncodable { 48| | var dateEncodingStrategy: AnyCodable.DateEncodingStrategy? { get } 49| | 50| | var value: Any { get } 51| | init(_ value: T?) 52| |} 53| | 54| |extension AnyEncodable: _AnyEncodable {} 55| | 56| |// MARK: - Encodable 57| | 58| |extension _AnyEncodable { 59| | // swiftlint:disable:next cyclomatic_complexity function_body_length 60| 3.16k| public func encode(to encoder: Encoder) throws { 61| 3.16k| if let date = self.value as? Date, let strategy = dateEncodingStrategy { 62| 812| try strategy(date, encoder) 63| 812| return 64| 2.35k| } 65| 2.35k| 66| 2.35k| var container = encoder.singleValueContainer() 67| 2.35k| switch self.value { 68| 2.35k| case let dictionary as [String: Any?]: 69| 2.40k| try container.encode(dictionary.mapValues { AnyCodable($0, dateEncodingStrategy: dateEncodingStrategy) }) 70| 2.35k| case let array as [Any?]: 71| 338| try container.encode(array.map { AnyCodable($0, dateEncodingStrategy: dateEncodingStrategy) }) 72| 2.35k| case let url as URL: 73| 0| try container.encode(url) 74| 2.35k| case let string as String: 75| 610| try container.encode(string) 76| 2.35k| case let date as Date: 77| 0| try container.encode(date) 78| 2.35k| case let int as Int: 79| 674| try container.encode(int) 80| 2.35k| case let int8 as Int8: 81| 0| try container.encode(int8) 82| 2.35k| case let int16 as Int16: 83| 0| try container.encode(int16) 84| 2.35k| case let int32 as Int32: 85| 0| try container.encode(int32) 86| 2.35k| case let int64 as Int64: 87| 0| try container.encode(int64) 88| 2.35k| case let uint as UInt: 89| 0| try container.encode(uint) 90| 2.35k| case let uint8 as UInt8: 91| 0| try container.encode(uint8) 92| 2.35k| case let uint16 as UInt16: 93| 0| try container.encode(uint16) 94| 2.35k| case let uint32 as UInt32: 95| 0| try container.encode(uint32) 96| 2.35k| case let uint64 as UInt64: 97| 0| try container.encode(uint64) 98| 2.35k| case let float as Float: 99| 0| try container.encode(float) 100| 2.35k| case let double as Double: 101| 2| try container.encode(double) 102| 2.35k| case let bool as Bool: 103| 2| try container.encode(bool) 104| 2.35k| case is Void: 105| 0| try container.encodeNil() 106| 2.35k| default: 107| 0| let context = EncodingError.Context(codingPath: container.codingPath, 108| 0| debugDescription: "AnyCodable value cannot be encoded") 109| 0| throw EncodingError.invalidValue(self.value, context) 110| 2.35k| } 111| 2.35k| } 112| 0| private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws { // swiftlint:disable:this cyclomatic_complexity line_length 113| 0| switch CFNumberGetType(nsnumber) { 114| 0| case .charType: 115| 0| try container.encode(nsnumber.boolValue) 116| 0| case .sInt8Type: 117| 0| try container.encode(nsnumber.int8Value) 118| 0| case .sInt16Type: 119| 0| try container.encode(nsnumber.int16Value) 120| 0| case .sInt32Type: 121| 0| try container.encode(nsnumber.int32Value) 122| 0| case .sInt64Type: 123| 0| try container.encode(nsnumber.int64Value) 124| 0| case .shortType: 125| 0| try container.encode(nsnumber.uint16Value) 126| 0| case .longType: 127| 0| try container.encode(nsnumber.uint32Value) 128| 0| case .longLongType: 129| 0| try container.encode(nsnumber.uint64Value) 130| 0| case .intType, .nsIntegerType, .cfIndexType: 131| 0| try container.encode(nsnumber.intValue) 132| 0| case .floatType, .float32Type: 133| 0| try container.encode(nsnumber.floatValue) 134| 0| case .doubleType, .float64Type, .cgFloatType: 135| 0| try container.encode(nsnumber.doubleValue) 136| 0| @unknown default: 137| 0| fatalError() 138| 0| } 139| 0| } 140| |} 141| | 142| |extension AnyEncodable: Equatable { 143| 0| public static func == (lhs: AnyEncodable, rhs: AnyEncodable) -> Bool { // swiftlint:disable:this cyclomatic_complexity line_length 144| 0| switch (lhs.value, rhs.value) { 145| 0| case is (Void, Void): 146| 0| return true 147| 0| case let (lhs as Bool, rhs as Bool): 148| 0| return lhs == rhs 149| 0| case let (lhs as Int, rhs as Int): 150| 0| return lhs == rhs 151| 0| case let (lhs as Int8, rhs as Int8): 152| 0| return lhs == rhs 153| 0| case let (lhs as Int16, rhs as Int16): 154| 0| return lhs == rhs 155| 0| case let (lhs as Int32, rhs as Int32): 156| 0| return lhs == rhs 157| 0| case let (lhs as Int64, rhs as Int64): 158| 0| return lhs == rhs 159| 0| case let (lhs as UInt, rhs as UInt): 160| 0| return lhs == rhs 161| 0| case let (lhs as UInt8, rhs as UInt8): 162| 0| return lhs == rhs 163| 0| case let (lhs as UInt16, rhs as UInt16): 164| 0| return lhs == rhs 165| 0| case let (lhs as UInt32, rhs as UInt32): 166| 0| return lhs == rhs 167| 0| case let (lhs as UInt64, rhs as UInt64): 168| 0| return lhs == rhs 169| 0| case let (lhs as Float, rhs as Float): 170| 0| return lhs == rhs 171| 0| case let (lhs as Double, rhs as Double): 172| 0| return lhs == rhs 173| 0| case let (lhs as String, rhs as String): 174| 0| return lhs == rhs 175| 0| case (let lhs as [String: AnyEncodable], let rhs as [String: AnyEncodable]): 176| 0| return lhs == rhs 177| 0| case (let lhs as [AnyEncodable], let rhs as [AnyEncodable]): 178| 0| return lhs == rhs 179| 0| default: 180| 0| return false 181| 0| } 182| 0| } 183| |} 184| | 185| |extension AnyEncodable: CustomStringConvertible { 186| 0| public var description: String { 187| 0| switch value { 188| 0| case is Void: 189| 0| return String(describing: nil as Any?) 190| 0| case let value as CustomStringConvertible: 191| 0| return value.description 192| 0| default: 193| 0| return String(describing: value) 194| 0| } 195| 0| } 196| |} 197| | 198| |extension AnyEncodable: CustomDebugStringConvertible { 199| 0| public var debugDescription: String { 200| 0| switch value { 201| 0| case let value as CustomDebugStringConvertible: 202| 0| return "AnyEncodable(\(value.debugDescription))" 203| 0| default: 204| 0| return "AnyEncodable(\(self.description))" 205| 0| } 206| 0| } 207| |} 208| | 209| |extension AnyEncodable: ExpressibleByNilLiteral {} 210| |extension AnyEncodable: ExpressibleByBooleanLiteral {} 211| |extension AnyEncodable: ExpressibleByIntegerLiteral {} 212| |extension AnyEncodable: ExpressibleByFloatLiteral {} 213| |extension AnyEncodable: ExpressibleByStringLiteral {} 214| |extension AnyEncodable: ExpressibleByArrayLiteral {} 215| |extension AnyEncodable: ExpressibleByDictionaryLiteral {} 216| | 217| |extension _AnyEncodable { 218| 0| public init(nilLiteral: ()) { 219| 0| self.init(nil as Any?) 220| 0| } 221| | 222| 2| public init(booleanLiteral value: Bool) { 223| 2| self.init(value) 224| 2| } 225| | 226| 3| public init(integerLiteral value: Int) { 227| 3| self.init(value) 228| 3| } 229| | 230| 2| public init(floatLiteral value: Double) { 231| 2| self.init(value) 232| 2| } 233| | 234| 0| public init(extendedGraphemeClusterLiteral value: String) { 235| 0| self.init(value) 236| 0| } 237| 4| public init(stringLiteral value: String) { 238| 4| self.init(value) 239| 4| } 240| | 241| 2| public init(arrayLiteral elements: Any...) { 242| 2| self.init(elements) 243| 2| } 244| | 245| 3| public init(dictionaryLiteral elements: (AnyHashable, Any)...) { 246| 3| self.init([AnyHashable: Any](elements, uniquingKeysWith: { (first, _) in first })) 247| 3| } 248| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Coding/Extensions.swift: 1| |// 2| |// Extensions.swift 3| |// 4| |// 5| |// Created by Pranjal Satija on 7/19/20. 6| |// 7| | 8| |import Foundation 9| | 10| |// MARK: Date 11| |internal extension Date { 12| 0| func parseFormatted() -> String { 13| 0| return ParseCoding.dateFormatter.string(from: self) 14| 0| } 15| | 16| 0| var parseRepresentation: [String: String] { 17| 0| return ["__type": "Date", "iso": parseFormatted()] 18| 0| } 19| |} 20| | 21| |// MARK: JSONEncoder 22| |extension JSONEncoder { 23| 0| func encodeAsString(_ value: T) throws -> String where T: Encodable { 24| 0| guard let string = String(data: try encode(value), encoding: .utf8) else { 25| 0| throw ParseError(code: .unknownError, message: "Unable to encode object...") 26| 0| } 27| 0| 28| 0| return string 29| 0| } 30| |} 31| | 32| |// MARK: ParseObject 33| |internal extension ParseObject { 34| 387| func getEncoder(skipKeys: Bool = true) -> ParseEncoder { 35| 387| return ParseCoding.parseEncoder(skipKeys: skipKeys) 36| 387| } 37| | 38| 41| func getDecoder() -> JSONDecoder { 39| 41| ParseCoding.jsonDecoder() 40| 41| } 41| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Coding/ParseCoding.swift: 1| |// 2| |// ParseCoding.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-07-24. 6| |// Copyright © 2017 Parse. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |// MARK: ParseCoding 12| |internal enum ParseCoding {} 13| | 14| |// MARK: Coders 15| |extension ParseCoding { 16| | private static let forbiddenKeys = Set(["createdAt", "updatedAt", "objectId", "className"]) 17| | 18| 1.35k| static func jsonEncoder() -> JSONEncoder { 19| 1.35k| let encoder = JSONEncoder() 20| 1.35k| encoder.dateEncodingStrategy = jsonDateEncodingStrategy 21| 1.35k| return encoder 22| 1.35k| } 23| | 24| 1.84k| static func jsonDecoder() -> JSONDecoder { 25| 1.84k| let decoder = JSONDecoder() 26| 1.84k| decoder.dateDecodingStrategy = dateDecodingStrategy 27| 1.84k| return decoder 28| 1.84k| } 29| | 30| 399| static func parseEncoder(skipKeys: Bool = true) -> ParseEncoder { 31| 399| ParseEncoder( 32| 399| dateEncodingStrategy: parseDateEncodingStrategy, 33| 399| skippingKeys: skipKeys ? forbiddenKeys : [] 34| 399| ) 35| 399| } 36| |} 37| | 38| |// MARK: Dates 39| |extension ParseCoding { 40| | enum DateEncodingKeys: String, CodingKey { 41| | case iso 42| | case type = "__type" 43| | } 44| | 45| 1| static let dateFormatter: DateFormatter = { 46| 1| var dateFormatter = DateFormatter() 47| 1| dateFormatter.locale = Locale(identifier: "") 48| 1| dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" 49| 1| return dateFormatter 50| 1| }() 51| | 52| | static let jsonDateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .custom(parseDateEncodingStrategy) 53| | 54| 2.48k| static let parseDateEncodingStrategy: AnyCodable.DateEncodingStrategy = { (date, encoder) in 55| 2.48k| var container = encoder.container(keyedBy: DateEncodingKeys.self) 56| 2.48k| try container.encode("Date", forKey: .type) 57| 2.48k| let dateString = dateFormatter.string(from: date) 58| 2.48k| try container.encode(dateString, forKey: .iso) 59| 2.48k| } 60| | 61| 3.88k| static let dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .custom({ (decoder) -> Date in 62| 3.88k| do { 63| 3.88k| let container = try decoder.singleValueContainer() 64| 3.88k| let decodedString = try container.decode(String.self) 65| 3.88k| 66| 3.88k| if let date = dateFormatter.date(from: decodedString) { 67| 0| return date 68| 3.88k| } else { 69| 3.88k| throw ParseError( 70| 3.88k| code: .unknownError, 71| 3.88k| message: "An invalid date string was provided when decoding dates." 72| 3.88k| ) 73| 3.88k| } 74| 3.88k| } catch let error { 75| 3.88k| let container = try decoder.container(keyedBy: DateEncodingKeys.self) 76| 3.88k| 77| 3.88k| if 78| 3.88k| let decoded = try container.decodeIfPresent(String.self, forKey: .iso), 79| 3.88k| let date = dateFormatter.date(from: decoded) 80| 3.88k| { 81| 3.88k| return date 82| 3.88k| } else { 83| 0| throw ParseError( 84| 0| code: .unknownError, 85| 0| message: "An invalid date string was provided when decoding dates." 86| 0| ) 87| 0| } 88| 0| } 89| 0| }) 90| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Coding/ParseEncoder.swift: 1| |// 2| |// ParseEncoder.swift 3| |// ParseSwift 4| |// 5| |// Created by Pranjal Satija on 7/20/20. 6| |// Copyright © 2020 Parse. All rights reserved. 7| |// 8| | 9| |// This rule doesn't allow types with underscores in their names. 10| |// swiftlint:disable type_name 11| | 12| |import Foundation 13| | 14| |// MARK: ParseEncoder 15| |internal struct ParseEncoder { 16| | let dateEncodingStrategy: AnyCodable.DateEncodingStrategy? 17| | let jsonEncoder: JSONEncoder 18| | let skippedKeys: Set 19| | 20| | init( 21| | dateEncodingStrategy: AnyCodable.DateEncodingStrategy? = nil, 22| | jsonEncoder: JSONEncoder = JSONEncoder(), 23| | skippingKeys: Set = [] 24| 401| ) { 25| 401| self.dateEncodingStrategy = dateEncodingStrategy 26| 401| self.jsonEncoder = jsonEncoder 27| 401| self.skippedKeys = skippingKeys 28| 401| } 29| | 30| 412| func encodeToDictionary(_ value: T) throws -> [AnyHashable: Any] { 31| 412| let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedKeys) 32| 412| try value.encode(to: encoder) 33| 412| 34| 412| // The encoder always uses an `NSDictionary` with string keys. 35| 412| // swiftlint:disable:next force_cast 36| 412| return encoder.dictionary as! [AnyHashable: Any] 37| 412| } 38| | 39| 390| func encode(_ value: T) throws -> Data { 40| 390| let dictionary = try encodeToDictionary(value) 41| 390| return try jsonEncoder.encode(AnyCodable(dictionary, dateEncodingStrategy: dateEncodingStrategy)) 42| 390| } 43| | 44| 11| func encode(_ array: [T]) throws -> Data { 45| 22| let dictionaries = try array.map { try encodeToDictionary($0) } 46| 11| return try jsonEncoder.encode(AnyCodable(dictionaries, dateEncodingStrategy: dateEncodingStrategy)) 47| 11| } 48| |} 49| | 50| |// MARK: _ParseEncoder 51| |internal struct _ParseEncoder: Encoder { 52| | let codingPath: [CodingKey] 53| | let dictionary: NSMutableDictionary 54| | let skippedKeys: Set 55| 1.04k| let userInfo: [CodingUserInfoKey: Any] = [:] 56| | 57| 1.04k| init(codingPath: [CodingKey], dictionary: NSMutableDictionary, skippingKeys: Set) { 58| 1.04k| self.codingPath = codingPath 59| 1.04k| self.dictionary = dictionary 60| 1.04k| self.skippedKeys = skippingKeys 61| 1.04k| } 62| | 63| 737| func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { 64| 737| let container = _ParseEncoderKeyedEncodingContainer( 65| 737| codingPath: codingPath, 66| 737| dictionary: dictionary, 67| 737| skippingKeys: skippedKeys 68| 737| ) 69| 737| 70| 737| return KeyedEncodingContainer(container) 71| 737| } 72| | 73| 1| func singleValueContainer() -> SingleValueEncodingContainer { 74| 1| _ParseEncoderSingleValueEncodingContainer( 75| 1| codingPath: codingPath, 76| 1| dictionary: dictionary, 77| 1| skippingKeys: skippedKeys 78| 1| ) 79| 1| } 80| | 81| 308| func unkeyedContainer() -> UnkeyedEncodingContainer { 82| 308| _ParseEncoderUnkeyedEncodingContainer( 83| 308| codingPath: codingPath, 84| 308| dictionary: dictionary, 85| 308| skippingKeys: skippedKeys 86| 308| ) 87| 308| } 88| | 89| | static func encode( 90| | _ value: T, 91| | with codingPath: [CodingKey], 92| | skippingKeys skippedKeys: Set 93| 2.71k| ) throws -> Any { 94| 2.71k| switch value { 95| 2.71k| case is Bool, is Int, is Int8, is Int16, is Int32, is Int64, is UInt, is UInt8, is UInt16, is UInt32, is UInt64, 96| 2.07k| is Float, is Double, is String, is Date: 97| 2.07k| return value 98| 2.71k| default: 99| 634| let dictionary = NSMutableDictionary() 100| 634| let encoder = _ParseEncoder(codingPath: codingPath, dictionary: dictionary, skippingKeys: skippedKeys) 101| 634| try value.encode(to: encoder) 102| 634| 103| 634| return codingPath.last.map { dictionary[$0.stringValue] ?? dictionary } ?? dictionary 104| 2.71k| } 105| 2.71k| } 106| |} 107| | 108| |// MARK: _ParseEncoderKeyedEncodingContainer 109| |internal struct _ParseEncoderKeyedEncodingContainer: KeyedEncodingContainerProtocol { 110| | let codingPath: [CodingKey] 111| | let dictionary: NSMutableDictionary 112| | let skippedKeys: Set 113| | 114| 737| init(codingPath: [CodingKey], dictionary: NSMutableDictionary, skippingKeys: Set) { 115| 737| self.codingPath = codingPath 116| 737| self.dictionary = dictionary 117| 737| self.skippedKeys = skippingKeys 118| 737| } 119| | 120| 0| mutating func encodeNil(forKey key: Key) throws { 121| 0| if skippedKeys.contains(key.stringValue) { return } 122| 0| 123| 0| dictionary[key.stringValue] = nil 124| 0| } 125| | 126| 2.40k| mutating func encode(_ value: T, forKey key: Key) throws where T: Encodable { 127| 2.40k| if skippedKeys.contains(key.stringValue) { return } 128| 2.39k| 129| 2.39k| dictionary[key.stringValue] = try _ParseEncoder.encode( 130| 2.39k| value, 131| 2.39k| with: codingPath + [key], 132| 2.39k| skippingKeys: skippedKeys 133| 2.39k| ) 134| 2.39k| } 135| | 136| | mutating func nestedContainer( 137| | keyedBy keyType: NestedKey.Type, 138| | forKey key: Key 139| 0| ) -> KeyedEncodingContainer where NestedKey: CodingKey { 140| 0| let container = _ParseEncoderKeyedEncodingContainer( 141| 0| codingPath: codingPath + [key], 142| 0| dictionary: dictionary, 143| 0| skippingKeys: skippedKeys 144| 0| ) 145| 0| 146| 0| return KeyedEncodingContainer(container) 147| 0| } 148| | 149| 0| mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { 150| 0| _ParseEncoderUnkeyedEncodingContainer( 151| 0| codingPath: codingPath + [key], 152| 0| dictionary: dictionary, 153| 0| skippingKeys: skippedKeys 154| 0| ) 155| 0| } 156| | 157| 0| mutating func superEncoder() -> Encoder { 158| 0| fatalError("You can't encode supertypes yet.") 159| 0| } 160| | 161| 0| mutating func superEncoder(forKey key: Key) -> Encoder { 162| 0| fatalError("You can't encode supertypes yet.") 163| 0| } 164| |} 165| | 166| |// MARK: _ParseEncoderSingleValueEncodingContainer 167| |internal struct _ParseEncoderSingleValueEncodingContainer: SingleValueEncodingContainer { 168| | let codingPath: [CodingKey] 169| | let dictionary: NSMutableDictionary 170| | let skippedKeys: Set 171| | 172| 1| init(codingPath: [CodingKey], dictionary: NSMutableDictionary, skippingKeys: Set) { 173| 1| self.codingPath = codingPath 174| 1| self.dictionary = dictionary 175| 1| self.skippedKeys = skippingKeys 176| 1| } 177| | 178| 1| var key: String { 179| 1| codingPath.last?.stringValue ?? "" 180| 1| } 181| | 182| 0| mutating func encodeNil() throws { 183| 0| dictionary[key] = nil 184| 0| } 185| | 186| 1| mutating func encode(_ value: T) throws where T: Encodable { 187| 1| dictionary[key] = try _ParseEncoder.encode(value, with: codingPath, skippingKeys: skippedKeys) 188| 1| } 189| |} 190| | 191| |// MARK: _ParseEncoderUnkeyedEncodingContainer 192| |internal struct _ParseEncoderUnkeyedEncodingContainer: UnkeyedEncodingContainer { 193| | let codingPath: [CodingKey] 194| | let dictionary: NSMutableDictionary 195| | let skippedKeys: Set 196| | 197| | var array: NSMutableArray { 198| 310| get { 199| 310| guard let array = dictionary[key] as? NSMutableArray else { 200| 0| fatalError("There's no array available for unkeyed encoding.") 201| 310| } 202| 310| 203| 310| return array 204| 310| } 205| | 206| 308| set { dictionary[key] = newValue } 207| | } 208| | 209| 0| var count: Int { 210| 0| array.count 211| 0| } 212| | 213| 618| var key: String { 214| 618| codingPath.last?.stringValue ?? "" 215| 618| } 216| | 217| 308| init(codingPath: [CodingKey], dictionary: NSMutableDictionary, skippingKeys: Set) { 218| 308| self.codingPath = codingPath 219| 308| self.dictionary = dictionary 220| 308| self.skippedKeys = skippingKeys 221| 308| 222| 308| self.array = NSMutableArray() 223| 308| } 224| | 225| 0| mutating func encodeNil() throws { 226| 0| array.add(NSNull()) 227| 0| } 228| | 229| 310| mutating func encode(_ value: T) throws where T: Encodable { 230| 310| let encoded = try _ParseEncoder.encode(value, with: codingPath, skippingKeys: skippedKeys) 231| 310| array.add(encoded) 232| 310| } 233| | 234| | mutating func nestedContainer( 235| | keyedBy keyType: NestedKey.Type 236| 0| ) -> KeyedEncodingContainer where NestedKey: CodingKey { 237| 0| let dictionary = NSMutableDictionary() 238| 0| array.add(dictionary) 239| 0| 240| 0| let container = _ParseEncoderKeyedEncodingContainer( 241| 0| codingPath: codingPath, 242| 0| dictionary: dictionary, 243| 0| skippingKeys: skippedKeys 244| 0| ) 245| 0| 246| 0| return KeyedEncodingContainer(container) 247| 0| } 248| | 249| 0| mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { 250| 0| let dictionary = NSMutableDictionary() 251| 0| array.add(dictionary) 252| 0| 253| 0| return _ParseEncoderUnkeyedEncodingContainer( 254| 0| codingPath: codingPath, 255| 0| dictionary: dictionary, 256| 0| skippingKeys: skippedKeys 257| 0| ) 258| 0| } 259| | 260| 0| mutating func superEncoder() -> Encoder { 261| 0| fatalError("You can't encode supertypes yet.") 262| 0| } 263| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Mutation Operations/ParseMutationContainer.swift: 1| |// 2| |// ParseMutationContainer.swift 3| |// Parse 4| |// 5| |// Created by Florent Vilmart on 17-07-24. 6| |// Copyright © 2017 Parse. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |public struct ParseMutationContainer: Encodable where T: ParseObject { 12| | typealias ObjectType = T 13| | 14| | var target: T 15| 0| private var operations = [String: Encodable]() 16| | 17| 0| init(target: T) { 18| 0| self.target = target 19| 0| } 20| | 21| 0| public mutating func increment(_ key: String, by amount: Int) { 22| 0| operations[key] = IncrementOperation(amount: amount) 23| 0| } 24| | 25| 0| public mutating func addUnique(_ key: String, objects: [W]) where W: Encodable, W: Hashable { 26| 0| operations[key] = AddUniqueOperation(objects: objects) 27| 0| } 28| | 29| | public mutating func addUnique(_ key: (String, WritableKeyPath), 30| 0| objects: [V]) where V: Encodable, V: Hashable { 31| 0| operations[key.0] = AddUniqueOperation(objects: objects) 32| 0| var values = target[keyPath: key.1] 33| 0| values.append(contentsOf: objects) 34| 0| target[keyPath: key.1] = Array(Set(values)) 35| 0| } 36| | 37| | public mutating func addUnique(_ key: (String, WritableKeyPath), 38| 0| objects: [V]) where V: Encodable, V: Hashable { 39| 0| operations[key.0] = AddUniqueOperation(objects: objects) 40| 0| var values = target[keyPath: key.1] ?? [] 41| 0| values.append(contentsOf: objects) 42| 0| target[keyPath: key.1] = Array(Set(values)) 43| 0| } 44| | 45| 0| public mutating func add(_ key: String, objects: [W]) where W: Encodable { 46| 0| operations[key] = AddOperation(objects: objects) 47| 0| } 48| | 49| | public mutating func add(_ key: (String, WritableKeyPath), 50| 0| objects: [V]) where V: Encodable { 51| 0| operations[key.0] = AddOperation(objects: objects) 52| 0| var values = target[keyPath: key.1] 53| 0| values.append(contentsOf: objects) 54| 0| target[keyPath: key.1] = values 55| 0| } 56| | 57| | public mutating func add(_ key: (String, WritableKeyPath), 58| 0| objects: [V]) where V: Encodable { 59| 0| operations[key.0] = AddOperation(objects: objects) 60| 0| var values = target[keyPath: key.1] ?? [] 61| 0| values.append(contentsOf: objects) 62| 0| target[keyPath: key.1] = values 63| 0| } 64| | 65| 0| public mutating func remove(_ key: String, objects: [W]) where W: Encodable { 66| 0| operations[key] = RemoveOperation(objects: objects) 67| 0| } 68| | 69| | public mutating func remove(_ key: (String, WritableKeyPath), 70| 0| objects: [V]) where V: Encodable, V: Hashable { 71| 0| operations[key.0] = RemoveOperation(objects: objects) 72| 0| let values = target[keyPath: key.1] 73| 0| var set = Set(values) 74| 0| objects.forEach { 75| 0| set.remove($0) 76| 0| } 77| 0| target[keyPath: key.1] = Array(set) 78| 0| } 79| | 80| | public mutating func remove(_ key: (String, WritableKeyPath), 81| 0| objects: [V]) where V: Encodable, V: Hashable { 82| 0| operations[key.0] = RemoveOperation(objects: objects) 83| 0| let values = target[keyPath: key.1] 84| 0| var set = Set(values ?? []) 85| 0| objects.forEach { 86| 0| set.remove($0) 87| 0| } 88| 0| target[keyPath: key.1] = Array(set) 89| 0| } 90| | 91| 0| public mutating func unset(_ key: String) { 92| 0| operations[key] = DeleteOperation() 93| 0| } 94| | 95| 0| public mutating func unset(_ key: (String, WritableKeyPath)) where V: Encodable { 96| 0| operations[key.0] = DeleteOperation() 97| 0| target[keyPath: key.1] = nil 98| 0| } 99| | 100| 0| public func encode(to encoder: Encoder) throws { 101| 0| var container = encoder.container(keyedBy: RawCodingKey.self) 102| 0| try operations.forEach { pair in 103| 0| let (key, value) = pair 104| 0| let encoder = container.superEncoder(forKey: .key(key)) 105| 0| try value.encode(to: encoder) 106| 0| } 107| 0| } 108| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Objects/ParseInstallation.swift: 1| |// 2| |// ParseInstallation.swift 3| |// ParseSwift 4| |// 5| |// Created by Corey Baker on 9/6/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |#if canImport(UIKit) 12| |import UIKit 13| |#else 14| |import AppKit 15| |#endif 16| | 17| |/** 18| | Objects that conform to the `ParseInstallation` protocol have a local representation of an 19| | installation persisted to the Parse cloud. This protocol inherits from the 20| | `ParseObject` protocol, and retains the same functionality of a `ParseObject`, but also extends 21| | it with installation-specific fields and related immutability and validity 22| | checks. 23| | 24| | A valid `ParseInstallation` can only be instantiated via 25| | `+current` because the required identifier fields 26| | are readonly. The `timeZone` and `badge` fields are also readonly properties which 27| | are automatically updated to match the device's time zone and application badge 28| | when the `ParseInstallation` is saved, thus these fields might not reflect the 29| | latest device state if the installation has not recently been saved. 30| | 31| | `ParseInstallation` objects which have a valid `deviceToken` and are saved to 32| | the Parse cloud can be used to target push notifications. 33| | 34| | - warning: Only use `ParseInstallation` objects on the main thread as they 35| | require UIApplication for `badge` 36| |*/ 37| |public protocol ParseInstallation: ParseObject { 38| | 39| | /** 40| | The device type for the `ParseInstallation`. 41| | */ 42| | var deviceType: String? { get set } 43| | 44| | /** 45| | The installationId for the `ParseInstallation`. 46| | */ 47| | var installationId: String? { get set } 48| | 49| | /** 50| | The device token for the `ParseInstallation`. 51| | */ 52| | var deviceToken: String? { get set } 53| | 54| | /** 55| | The badge for the `ParseInstallation`. 56| | */ 57| | var badge: Int? { get set } 58| | 59| | /** 60| | The name of the time zone for the `ParseInstallation`. 61| | */ 62| | var timeZone: String? { get set } 63| | 64| | /** 65| | The channels for the `ParseInstallation`. 66| | */ 67| | var channels: [String]? { get set } 68| | 69| | var appName: String? { get set } 70| | 71| | var appIdentifier: String? { get set } 72| | 73| | var appVersion: String? { get set } 74| | 75| | var parseVersion: String? { get set } 76| | 77| | var localeIdentifier: String? { get set } 78| |} 79| | 80| |// MARK: Default Implementations 81| |public extension ParseInstallation { 82| 52| static var className: String { 83| 52| return "_Installation" 84| 52| } 85| |} 86| | 87| |// MARK: CurrentInstallationContainer 88| |struct CurrentInstallationContainer: Codable { 89| | var currentInstallation: T? 90| | var installationId: String? 91| |} 92| | 93| |// MARK: Current Installation Support 94| |extension ParseInstallation { 95| | static var currentInstallationContainer: CurrentInstallationContainer { 96| 2.44k| get { 97| 2.44k| if let installation: CurrentInstallationContainer = 98| 2.44k| try? ParseStorage.shared.secureStore.get(valueFor: ParseStorage.Keys.currentInstallation) { 99| 2.36k| return installation 100| 2.36k| } else { 101| 81| var newInstallation = CurrentInstallationContainer() 102| 81| let newInstallationId = UUID().uuidString.lowercased() 103| 81| newInstallation.installationId = newInstallationId 104| 81| newInstallation.currentInstallation?.createInstallationId(newId: newInstallationId) 105| 81| newInstallation.currentInstallation?.updateAutomaticInfo() 106| 81| try? ParseStorage.shared.secureStore.set(newInstallation, for: ParseStorage.Keys.currentInstallation) 107| 81| return newInstallation 108| 81| } 109| 0| } 110| | 111| 335| set { 112| 335| try? ParseStorage.shared.secureStore.set(newValue, for: ParseStorage.Keys.currentInstallation) 113| 335| } 114| | } 115| | 116| 101| internal static func updateInternalFieldsCorrectly() { 117| 101| if Self.currentInstallationContainer.currentInstallation?.installationId != 118| 101| Self.currentInstallationContainer.installationId! { 119| 82| //If the user made changes, set back to the original 120| 82| Self.currentInstallationContainer.currentInstallation?.installationId = 121| 82| Self.currentInstallationContainer.installationId! 122| 101| } 123| 101| //Always pull automatic info to ensure user made no changes to immutable values 124| 101| Self.currentInstallationContainer.currentInstallation?.updateAutomaticInfo() 125| 101| } 126| | 127| 92| internal static func saveCurrentContainer() { 128| 92| //Only save the BaseParseInstallation to keep memory footprint finite 129| 92| guard let currentInstallation: CurrentInstallationContainer 130| 92| = try? ParseStorage.shared.secureStore.get(valueFor: ParseStorage.Keys.currentInstallation) else { 131| 0| return 132| 92| } 133| 92| 134| 92| try? ParseStorage.shared.secureStore.set(currentInstallation, for: ParseStorage.Keys.currentInstallation) 135| 92| } 136| | 137| | /** 138| | Gets/Sets properties of the current installation in the shared secure store. 139| | 140| | - returns: Returns a `ParseInstallation` that is the current device. If there is none, returns `nil`. 141| | */ 142| | public static var current: Self? { 143| 51| get { 144| 51| Self.currentInstallationContainer.currentInstallation?.updateBadgeFromDevice() 145| 51| return Self.currentInstallationContainer.currentInstallation 146| 51| } 147| 101| set { 148| 101| Self.currentInstallationContainer.currentInstallation = newValue 149| 101| Self.updateInternalFieldsCorrectly() 150| 101| } 151| | } 152| |} 153| | 154| |// MARK: Automatic Info 155| |extension ParseInstallation { 156| 100| mutating func updateAutomaticInfo() { 157| 100| updateDeviceTypeFromDevice() 158| 100| updateTimeZoneFromDevice() 159| 100| updateBadgeFromDevice() 160| 100| updateVersionInfoFromDevice() 161| 100| updateLocaleIdentifierFromDevice() 162| 100| } 163| | 164| 0| mutating func createInstallationId(newId: String) { 165| 0| if installationId == nil { 166| 0| installationId = newId 167| 0| } 168| 0| } 169| | 170| 100| mutating func updateDeviceTypeFromDevice() { 171| 100| 172| 100| if deviceType != ParseConstants.deviceType { 173| 81| deviceType = ParseConstants.deviceType 174| 100| } 175| 100| } 176| | 177| 100| mutating func updateTimeZoneFromDevice() { 178| 100| let currentTimeZone = TimeZone.current.identifier 179| 100| if timeZone != currentTimeZone { 180| 81| timeZone = currentTimeZone 181| 100| } 182| 100| } 183| | 184| 150| mutating func updateBadgeFromDevice() { 185| 150| let applicationBadge: Int! 186| 150| 187| 150| #if canImport(UIKit) && !os(watchOS) 188| 150| applicationBadge = UIApplication.shared.applicationIconBadgeNumber 189| 150| #elseif canImport(AppKit) 190| 150| guard let currentApplicationBadge = NSApplication.shared.dockTile.badgeLabel else { 191| 150| //If badgeLabel not set, assume it's 0 192| 150| applicationBadge = 0 193| 150| return 194| 150| } 195| 150| applicationBadge = Int(currentApplicationBadge) 196| 150| #else 197| 150| applicationBadge = 0 198| 150| #endif 199| 150| 200| 150| if badge != applicationBadge { 201| 82| badge = applicationBadge 202| 82| //Since this changes, update secure storage whenever it changes 203| 82| Self.saveCurrentContainer() 204| 150| } 205| 150| } 206| | 207| 100| mutating func updateVersionInfoFromDevice() { 208| 100| guard let appInfo = Bundle.main.infoDictionary else { 209| 0| return 210| 100| } 211| 100| 212| 100| #if TARGET_OS_MACCATALYST 213| 100| // If using an Xcode new enough to know about Mac Catalyst: 214| 100| // Mac Catalyst Apps use a prefix to the bundle ID. This should not be transmitted 215| 100| // to Parse Server. Catalyst apps should look like iOS apps otherwise 216| 100| // push and other services don't work properly. 217| 100| if let currentAppIdentifier = appInfo[String(kCFBundleIdentifierKey)] as? String { 218| 100| let macCatalystBundleIdPrefix = "maccatalyst." 219| 100| if currentAppIdentifier.hasPrefix(macCatalystBundleIdPrefix) { 220| 100| appIdentifier = currentAppIdentifier.replacingOccurrences(of: macCatalystBundleIdPrefix, with: "") 221| 100| } 222| 100| } 223| 100| 224| 100| #else 225| 100| if let currentAppIdentifier = appInfo[String(kCFBundleIdentifierKey)] as? String { 226| 100| if appIdentifier != currentAppIdentifier { 227| 81| appIdentifier = currentAppIdentifier 228| 100| } 229| 100| } 230| 100| #endif 231| 100| 232| 100| if let currentAppName = appInfo[String(kCFBundleNameKey)] as? String { 233| 100| if appName != currentAppName { 234| 81| appName = currentAppName 235| 100| } 236| 100| } 237| 100| 238| 100| if let currentAppVersion = appInfo[String(kCFBundleVersionKey)] as? String { 239| 100| if appVersion != currentAppVersion { 240| 81| appVersion = currentAppVersion 241| 100| } 242| 100| } 243| 100| 244| 100| if parseVersion != ParseConstants.parseVersion { 245| 81| parseVersion = ParseConstants.parseVersion 246| 100| } 247| 100| } 248| | 249| | /** 250| | Save localeIdentifier in the following format: [language code]-[COUNTRY CODE]. 251| | 252| | The language codes are two-letter lowercase ISO language codes (such as "en") as defined by 253| | ISO 639-1. 254| | The country codes are two-letter uppercase ISO country codes (such as "US") as defined by 255| | ISO 3166-1. 256| | 257| | Many iOS locale identifiers don't contain the country code -> inconsistencies with Android/Windows Phone. 258| | */ 259| 100| mutating func updateLocaleIdentifierFromDevice() { 260| 100| guard let language = Locale.current.languageCode else { 261| 0| return 262| 100| } 263| 100| 264| 100| let currentLocalIdentifier: String! 265| 100| if let regionCode = Locale.current.regionCode { 266| 100| currentLocalIdentifier = "\(language)-\(regionCode)" 267| 100| } else { 268| 0| currentLocalIdentifier = language 269| 100| } 270| 100| 271| 100| if localeIdentifier != currentLocalIdentifier { 272| 81| localeIdentifier = currentLocalIdentifier 273| 100| } 274| 100| } 275| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Objects/ParseObject.swift: 1| |// 2| |// ParseObject.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-07-24. 6| |// Copyright © 2020 Parse. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |/** 12| | Objects that conform to the `ParseObject` protocol have a local representation of data persisted to the Parse cloud. 13| | This is the main protocol that is used to interact with objects in your app. 14| |*/ 15| |public protocol ParseObject: Fetchable, Saveable, CustomDebugStringConvertible { 16| | /** 17| | The class name of the object. 18| | */ 19| | static var className: String { get } 20| | 21| | /** 22| | The id of the object. 23| | */ 24| | var objectId: String? { get set } 25| | 26| | /** 27| | When the object was created. 28| | */ 29| | var createdAt: Date? { get set } 30| | 31| | /** 32| | When the object was last updated. 33| | */ 34| | var updatedAt: Date? { get set } 35| | 36| | /** 37| | The ACL for this object. 38| | */ 39| | var ACL: ParseACL? { get set } 40| |} 41| | 42| |// MARK: Default Implementations 43| |extension ParseObject { 44| | /** 45| | The class name of the object. 46| | */ 47| 3.40k| public static var className: String { 48| 3.40k| let classType = "\(type(of: self))" 49| 3.40k| return classType.components(separatedBy: ".").first! // strip .Type 50| 3.40k| } 51| | 52| | /** 53| | The class name of the object. 54| | */ 55| 3.98k| public var className: String { 56| 3.98k| return Self.className 57| 3.98k| } 58| | 59| | /** 60| | Determines if an object has the same objectId 61| | 62| | - parameter as: object to compare 63| | 64| | - returns: Returns a `true` if the other object has the same `objectId` or `false` if unsuccessful. 65| | */ 66| 1.04k| public func hasSameObjectId(as other: T) -> Bool { 67| 1.04k| return other.className == className && other.objectId == objectId && objectId != nil 68| 1.04k| } 69| |} 70| | 71| |// MARK: Batch Support 72| |public extension Sequence where Element: ParseObject { 73| | 74| | /** 75| | Saves a collection of objects *synchronously* all at once and throws an error if necessary. 76| | 77| | - parameter options: A set of options used to save objects. Defaults to an empty set. 78| | 79| | - returns: Returns a Result enum with the object if a save was successful or a `ParseError` if it failed. 80| | - throws: `ParseError` 81| | */ 82| 10| func saveAll(options: API.Options = []) throws -> [(Result)] { 83| 20| let commands = map { $0.saveCommand() } 84| 10| return try API.Command 85| 10| .batch(commands: commands) 86| 10| .execute(options: options) 87| 10| } 88| | 89| | /** 90| | Saves a collection of objects all at once *asynchronously* and executes the completion block when done. 91| | 92| | - parameter options: A set of options used to save objects. Defaults to an empty set. 93| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 94| | - parameter completion: The block to execute. 95| | It should have the following argument signature: `(Result<[(Result)], ParseError>)`. 96| | */ 97| | func saveAll( 98| | options: API.Options = [], 99| | callbackQueue: DispatchQueue = .main, 100| | completion: @escaping (Result<[(Result)], ParseError>) -> Void 101| 404| ) { 102| 807| let commands = map { $0.saveCommand() } 103| 404| API.Command 104| 404| .batch(commands: commands) 105| 404| .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) 106| 404| } 107| |} 108| | 109| |// MARK: Convenience 110| |extension ParseObject { 111| 1.86k| var endpoint: API.Endpoint { 112| 1.86k| if let objectId = objectId { 113| 1.24k| return .object(className: className, objectId: objectId) 114| 1.24k| } 115| 618| 116| 618| return .objects(className: className) 117| 1.86k| } 118| | 119| 1.86k| var isSaved: Bool { 120| 1.86k| return objectId != nil 121| 1.86k| } 122| |} 123| | 124| |// MARK: CustomDebugStringConvertible 125| |extension ParseObject { 126| 15| public var debugDescription: String { 127| 15| guard let descriptionData = try? ParseCoding.jsonEncoder().encode(self), 128| 15| let descriptionString = String(data: descriptionData, encoding: .utf8) else { 129| 0| return "\(className) ()" 130| 15| } 131| 15| 132| 15| return "\(className) (\(descriptionString))" 133| 15| } 134| |} 135| | 136| |// MARK: Fetchable 137| |extension ParseObject { 138| 1.23k| internal static func updateSecureStorageIfNeeded(_ results: [Self], saving: Bool = false) throws { 139| 1.23k| guard let currentUser = BaseParseUser.current else { 140| 1.22k| return 141| 1.22k| } 142| 12| 143| 12| var foundCurrentUserObjects = results.filter { $0.hasSameObjectId(as: currentUser) } 144| 12| foundCurrentUserObjects = try foundCurrentUserObjects.sorted(by: { 145| 0| if $0.updatedAt == nil || $1.updatedAt == nil { 146| 0| throw ParseError(code: .unknownError, 147| 0| message: "Objects from the server should always have an 'updatedAt'") 148| 0| } 149| 0| return $0.updatedAt!.compare($1.updatedAt!) == .orderedDescending 150| 0| }) 151| 12| if let foundCurrentUser = foundCurrentUserObjects.first { 152| 4| let encoded = try ParseCoding.parseEncoder(skipKeys: false).encode(foundCurrentUser) 153| 4| let updatedCurrentUser = try ParseCoding.jsonDecoder().decode(BaseParseUser.self, from: encoded) 154| 4| BaseParseUser.current = updatedCurrentUser 155| 4| BaseParseUser.saveCurrentUserContainer() 156| 12| } else if results.first?.className == BaseParseInstallation.className { 157| 8| guard let currentInstallation = BaseParseInstallation.current else { 158| 0| return 159| 8| } 160| 8| var saveInstallation: Self? 161| 8| let foundCurrentInstallationObjects = results.filter { $0.hasSameObjectId(as: currentInstallation) } 162| 8| if let foundCurrentInstallation = foundCurrentInstallationObjects.first { 163| 3| saveInstallation = foundCurrentInstallation 164| 8| } else { 165| 5| saveInstallation = results.first 166| 8| } 167| 8| if saveInstallation != nil { 168| 8| let encoded = try ParseCoding.parseEncoder(skipKeys: false).encode(saveInstallation!) 169| 8| let updatedCurrentInstallation = 170| 8| try ParseCoding.jsonDecoder().decode(BaseParseInstallation.self, from: encoded) 171| 8| BaseParseInstallation.current = updatedCurrentInstallation 172| 8| BaseParseInstallation.saveCurrentContainer() 173| 8| } 174| 12| } 175| 12| } 176| | 177| | /** 178| | Fetches the ParseObject *synchronously* with the current data from the server and sets an error if it occurs. 179| | 180| | - parameter options: A set of options used to save objects. Defaults to an empty set. 181| | - throws: An Error of `ParseError` type. 182| | */ 183| 6| public func fetch(options: API.Options = []) throws -> Self { 184| 6| let result: Self = try fetchCommand().execute(options: options) 185| 6| try? Self.updateSecureStorageIfNeeded([result]) 186| 6| return result 187| 6| } 188| | 189| | /** 190| | Fetches the `ParseObject` *asynchronously* and executes the given callback block. 191| | 192| | - parameter options: A set of options used to save objects. Defaults to an empty set. 193| | - parameter callbackQueue: The queue to return to after completion. Default 194| | value of .main. 195| | - parameter completion: The block to execute when completed. 196| | It should have the following argument signature: `(Result)`. 197| | */ 198| | public func fetch( 199| | options: API.Options = [], 200| | callbackQueue: DispatchQueue = .main, 201| | completion: @escaping (Result) -> Void 202| 404| ) { 203| 404| do { 204| 404| try fetchCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in 205| 404| if case .success(let foundResult) = result { 206| 404| try? Self.updateSecureStorageIfNeeded([foundResult]) 207| 404| } 208| 404| completion(result) 209| 404| } 210| 404| } catch let error as ParseError { 211| 0| completion(.failure(error)) 212| 0| } catch { 213| 0| completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) 214| 404| } 215| 404| } 216| | 217| 412| internal func fetchCommand() throws -> API.Command { 218| 412| return try API.Command.fetchCommand(self) 219| 412| } 220| |} 221| | 222| |// MARK: Mutations 223| |public extension ParseObject { 224| 0| var mutationContainer: ParseMutationContainer { 225| 0| return ParseMutationContainer(target: self) 226| 0| } 227| |} 228| | 229| |// MARK: Queryable 230| |public extension ParseObject { 231| | 232| 307| static func query() -> Query { 233| 307| Query() 234| 307| } 235| | 236| 1| static func query(_ constraints: QueryConstraint...) -> Query { 237| 1| Query(constraints) 238| 1| } 239| | 240| 1| static func query(_ constraints: [QueryConstraint]) -> Query { 241| 1| Query(constraints) 242| 1| } 243| |} 244| | 245| |// MARK: Saveable 246| |extension ParseObject { 247| | 248| | /** 249| | Saves the `ParseObject` *synchronously* and thows an error if there's an issue. 250| | 251| | - parameter options: A set of options used to save objects. Defaults to an empty set. 252| | - throws: A Error of type `ParseError`. 253| | 254| | - returns: Returns saved `ParseObject`. 255| | */ 256| 12| public func save(options: API.Options = []) throws -> Self { 257| 12| let result: Self = try saveCommand().execute(options: options) 258| 12| try? Self.updateSecureStorageIfNeeded([result], saving: true) 259| 12| return result 260| 12| } 261| | 262| | /** 263| | Saves the `ParseObject` *asynchronously* and executes the given callback block. 264| | 265| | - parameter options: A set of options used to save objects. Defaults to an empty set. 266| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 267| | - parameter completion: The block to execute. 268| | It should have the following argument signature: `(Result)`. 269| | */ 270| | public func save( 271| | options: API.Options = [], 272| | callbackQueue: DispatchQueue = .main, 273| | completion: @escaping (Result) -> Void 274| 608| ) { 275| 608| saveCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in 276| 608| if case .success(let foundResults) = result { 277| 608| try? Self.updateSecureStorageIfNeeded([foundResults], saving: true) 278| 608| } 279| 608| completion(result) 280| 608| } 281| 608| } 282| | 283| 1.45k| internal func saveCommand() -> API.Command { 284| 1.45k| return API.Command.saveCommand(self) 285| 1.45k| } 286| |} 287| | 288| |public extension ParseObject { 289| 0| func toPointer() -> Pointer { 290| 0| return Pointer(self) 291| 0| } 292| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Objects/ParseUser.swift: 1| |import Foundation 2| | 3| |/** 4| | Objects that conform to the `ParseUser` protocol have a local representation of a user persisted to the Parse Data. 5| | This protocol inherits from the `ParseObject` protocol, and retains the same functionality of a `ParseObject`, 6| | but also extends it with various user specific methods, like authentication, signing up, and validation uniqueness. 7| |*/ 8| |public protocol ParseUser: ParseObject { 9| | /** 10| | The username for the `ParseUser`. 11| | */ 12| | var username: String? { get set } 13| | 14| | /** 15| | The email for the `ParseUser`. 16| | */ 17| | var email: String? { get set } 18| | 19| | /** 20| | The password for the `ParseUser`. 21| | 22| | This will not be filled in from the server with the password. 23| | It is only meant to be set. 24| | */ 25| | var password: String? { get set } 26| |} 27| | 28| |// MARK: Default Implementations 29| |public extension ParseUser { 30| 855| static var className: String { 31| 855| return "_User" 32| 855| } 33| |} 34| | 35| |// MARK: CurrentUserContainer 36| |struct CurrentUserContainer: Codable { 37| | var currentUser: T? 38| | var sessionToken: String? 39| |} 40| | 41| |// MARK: Current User Support 42| |extension ParseUser { 43| | static var currentUserContainer: CurrentUserContainer? { 44| 3.08k| get { try? ParseStorage.shared.secureStore.get(valueFor: ParseStorage.Keys.currentUser) } 45| 29| set { try? ParseStorage.shared.secureStore.set(newValue, for: ParseStorage.Keys.currentUser)} 46| | } 47| | 48| 27| internal static func saveCurrentUserContainer() { 49| 27| //Only save the BaseParseUser to keep memory footprint finite 50| 27| guard let currentUser: CurrentUserContainer = 51| 27| try? ParseStorage.shared.secureStore.get(valueFor: ParseStorage.Keys.currentUser) else { 52| 0| return 53| 27| } 54| 27| 55| 27| try? ParseStorage.shared.secureStore.set(currentUser, for: ParseStorage.Keys.currentUser) 56| 27| } 57| | 58| | /** 59| | Gets the currently logged in user from the shared secure store and returns an instance of it. 60| | 61| | - returns: Returns a `ParseUser` that is the currently logged in user. If there is none, returns `nil`. 62| | */ 63| | public static var current: Self? { 64| 1.28k| get { Self.currentUserContainer?.currentUser } 65| 5| set { Self.currentUserContainer?.currentUser = newValue } 66| | } 67| | 68| | /** 69| | The session token for the `ParseUser`. 70| | 71| | This is set by the server upon successful authentication. 72| | */ 73| 18| public var sessionToken: String? { 74| 18| Self.currentUserContainer?.sessionToken 75| 18| } 76| |} 77| | 78| |// MARK: Logging In 79| |extension ParseUser { 80| | 81| | /** 82| | Makes a *synchronous* request to login a user with specified credentials. 83| | 84| | Returns an instance of the successfully logged in `ParseUser`. 85| | This also caches the user locally so that calls to `+current` will use the latest logged in user. 86| | 87| | - parameter username: The username of the user. 88| | - parameter password: The password of the user. 89| | - parameter error: The error object to set on error. 90| | 91| | - throws: An error of type `ParseUser`. 92| | - returns: An instance of the logged in `ParseUser`. 93| | If login failed due to either an incorrect password or incorrect username, it throws a `ParseError`. 94| | */ 95| | public static func login(username: String, 96| 17| password: String) throws -> Self { 97| 17| return try loginCommand(username: username, password: password).execute(options: []) 98| 17| } 99| | 100| | /** 101| | Makes an *asynchronous* request to log in a user with specified credentials. 102| | Returns an instance of the successfully logged in `ParseUser`. 103| | 104| | This also caches the user locally so that calls to `+current` will use the latest logged in user. 105| | - parameter username: The username of the user. 106| | - parameter password: The password of the user. 107| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 108| | - parameter completion: The block to execute. 109| | It should have the following argument signature: `(Result)`. 110| | */ 111| | public static func login( 112| | username: String, 113| | password: String, 114| | callbackQueue: DispatchQueue = .main, 115| | completion: @escaping (Result) -> Void 116| 1| ) { 117| 1| return loginCommand(username: username, password: password) 118| 1| .executeAsync(options: [], callbackQueue: callbackQueue, completion: completion) 119| 1| } 120| | 121| | private static func loginCommand(username: String, 122| 18| password: String) -> API.Command { 123| 18| let params = [ 124| 18| "username": username, 125| 18| "password": password 126| 18| ] 127| 18| 128| 18| return API.Command(method: .GET, 129| 18| path: .login, 130| 18| params: params) { (data) -> Self in 131| 18| let user = try ParseCoding.jsonDecoder().decode(Self.self, from: data) 132| 18| let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) 133| 18| 134| 18| Self.currentUserContainer = .init( 135| 18| currentUser: user, 136| 18| sessionToken: response.sessionToken 137| 18| ) 138| 18| Self.saveCurrentUserContainer() 139| 18| return user 140| 18| } 141| 18| } 142| |} 143| | 144| |// MARK: Logging Out 145| |extension ParseUser { 146| | 147| | /** 148| | Logs out the currently logged in user in the shared secure store *synchronously*. 149| | */ 150| 1| public static func logout() throws { 151| 1| _ = try logoutCommand().execute(options: []) 152| 1| } 153| | 154| | /** 155| | Logs out the currently logged in user *asynchronously*. 156| | 157| | This will also remove the session from the shared secure store, log out of linked services 158| | and all future calls to `current` will return `nil`. This is preferable to using `logout`, 159| | unless your code is already running from a background thread. 160| | 161| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 162| | - parameter completion: A block that will be called when logging out, completes or fails. 163| | */ 164| | public static func logout(callbackQueue: DispatchQueue = .main, 165| 1| completion: @escaping (Result) -> Void) { 166| 1| logoutCommand().executeAsync(options: [], callbackQueue: callbackQueue) { result in 167| 1| completion(result.map { true }) 168| 1| } 169| 1| } 170| | 171| 2| private static func logoutCommand() -> API.Command { 172| 2| return API.Command(method: .POST, path: .logout) { (_) -> Void in 173| 2| currentUserContainer = nil 174| 2| try? ParseStorage.shared.secureStore.delete(valueFor: ParseStorage.Keys.currentUser) 175| 2| } 176| 2| } 177| |} 178| | 179| |// MARK: Signing Up 180| |extension ParseUser { 181| | /** 182| | Signs up the user *synchronously*. 183| | 184| | This will also enforce that the username isn't already taken. 185| | 186| | - warning: Make sure that password and username are set before calling this method. 187| | 188| | - returns: Returns whether the sign up was successful. 189| | */ 190| | public static func signup(username: String, 191| 3| password: String) throws -> Self { 192| 3| return try signupCommand(username: username, password: password).execute(options: []) 193| 3| } 194| | 195| | /** 196| | Signs up the user *synchronously*. 197| | 198| | This will also enforce that the username isn't already taken. 199| | 200| | - warning: Make sure that password and username are set before calling this method. 201| | 202| | - returns: Returns whether the sign up was successful. 203| | */ 204| 0| public func signup() throws -> Self { 205| 0| return try signupCommand().execute(options: []) 206| 0| } 207| | 208| | /** 209| | Signs up the user *asynchronously*. 210| | 211| | This will also enforce that the username isn't already taken. 212| | 213| | - warning: Make sure that password and username are set before calling this method. 214| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 215| | - parameter completion: The block to execute. 216| | It should have the following argument signature: `(Result)`. 217| | */ 218| | public func signup(callbackQueue: DispatchQueue = .main, 219| 0| completion: @escaping (Result) -> Void) { 220| 0| return signupCommand().executeAsync(options: [], callbackQueue: callbackQueue, completion: completion) 221| 0| } 222| | 223| | /** 224| | Signs up the user *asynchronously*. 225| | 226| | This will also enforce that the username isn't already taken. 227| | 228| | - warning: Make sure that password and username are set before calling this method. 229| | 230| | - parameter username: The username of the user. 231| | - parameter password: The password of the user. 232| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 233| | - parameter completion: The block to execute. 234| | It should have the following argument signature: `(Result)`. 235| | */ 236| | public static func signup( 237| | username: String, 238| | password: String, 239| | callbackQueue: DispatchQueue = .main, 240| | completion: @escaping (Result) -> Void 241| 1| ) { 242| 1| return signupCommand(username: username, password: password) 243| 1| .executeAsync(options: [], callbackQueue: callbackQueue, completion: completion) 244| 1| } 245| | 246| | private static func signupCommand(username: String, 247| 4| password: String) -> API.Command { 248| 4| 249| 4| let body = SignupBody(username: username, password: password) 250| 4| return API.Command(method: .POST, path: .signup, body: body) { (data) -> Self in 251| 4| let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) 252| 4| var user = try ParseCoding.jsonDecoder().decode(Self.self, from: data) 253| 4| user.username = username 254| 4| user.password = password 255| 4| user.updatedAt = response.updatedAt ?? response.createdAt 256| 4| 257| 4| Self.currentUserContainer = .init( 258| 4| currentUser: user, 259| 4| sessionToken: response.sessionToken 260| 4| ) 261| 4| Self.saveCurrentUserContainer() 262| 4| return user 263| 4| } 264| 4| } 265| | 266| 0| private func signupCommand() -> API.Command { 267| 0| var user = self 268| 0| return API.Command(method: .POST, path: .signup, body: user) { (data) -> Self in 269| 0| let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) 270| 0| user.updatedAt = response.updatedAt ?? response.createdAt 271| 0| user.createdAt = response.createdAt 272| 0| 273| 0| Self.currentUserContainer = .init( 274| 0| currentUser: user, 275| 0| sessionToken: response.sessionToken 276| 0| ) 277| 0| Self.saveCurrentUserContainer() 278| 0| return user 279| 0| } 280| 0| } 281| |} 282| | 283| |// MARK: LoginSignupResponse 284| |private struct LoginSignupResponse: Codable { 285| | let createdAt: Date 286| | let objectId: String 287| | let sessionToken: String 288| | var updatedAt: Date? 289| |} 290| | 291| |// MARK: SignupBody 292| |private struct SignupBody: Codable { 293| | let username: String 294| | let password: String 295| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Objects/Protocols/Fetchable.swift: 1| |// 2| |// Fetchable.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-07-24. 6| |// Copyright © 2020 Parse. All rights reserved. 7| |// 8| | 9| |public protocol Fetchable: Codable { 10| | associatedtype FetchingType 11| | 12| | func fetch(options: API.Options) throws -> FetchingType 13| | func fetch() throws -> FetchingType 14| |} 15| | 16| |extension Fetchable { 17| 0| public func fetch() throws -> FetchingType { 18| 0| return try fetch(options: []) 19| 0| } 20| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Objects/Protocols/Queryable.swift: 1| |// 2| |// Queryable.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-07-24. 6| |// Copyright © 2020 Parse. All rights reserved. 7| |// 8| |import Foundation 9| | 10| |public protocol Queryable { 11| | associatedtype ResultType 12| | 13| | func find(options: API.Options) throws -> [ResultType] 14| | func first(options: API.Options) throws -> ResultType? 15| | func count(options: API.Options) throws -> Int 16| | func find(options: API.Options, callbackQueue: DispatchQueue, 17| | completion: @escaping (Result<[ResultType], ParseError>) -> Void) 18| | func first(options: API.Options, callbackQueue: DispatchQueue, 19| | completion: @escaping (Result) -> Void) 20| | func count(options: API.Options, callbackQueue: DispatchQueue, 21| | completion: @escaping (Result) -> Void) 22| |} 23| | 24| |extension Queryable { 25| | /** 26| | Finds objects *synchronously* based on the constructed query and sets an error if there was one. 27| | 28| | - throws: An error of type `ParseError`. 29| | 30| | - returns: Returns an array of `ParseObject`s that were found. 31| | */ 32| 0| func find() throws -> [ResultType] { 33| 0| return try find(options: []) 34| 0| } 35| | 36| | /** 37| | Gets an object *synchronously* based on the constructed query and sets an error if any occurred. 38| | 39| | - warning: This method mutates the query. It will reset the limit to `1`. 40| | 41| | - throws: An error of type `ParseError`. 42| | 43| | - returns: Returns a `ParseObject`, or `nil` if none was found. 44| | */ 45| 0| func first() throws -> ResultType? { 46| 0| return try first(options: []) 47| 0| } 48| | 49| | /** 50| | Counts objects *synchronously* based on the constructed query and sets an error if there was one. 51| | 52| | - throws: An error of type `ParseError`. 53| | 54| | - returns: Returns the number of `ParseObject`s that match the query, or `-1` if there is an error. 55| | */ 56| 0| func count() throws -> Int { 57| 0| return try count(options: []) 58| 0| } 59| | 60| | /** 61| | Finds objects *asynchronously* and calls the given block with the results. 62| | 63| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 64| | - parameter completion: The block to execute. 65| | It should have the following argument signature: `(Result<[ResultType], ParseError>)` 66| | */ 67| 0| func find(callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[ResultType], ParseError>) -> Void) { 68| 0| find(options: [], callbackQueue: callbackQueue, completion: completion) 69| 0| } 70| | 71| | /** 72| | Gets an object *asynchronously* and calls the given block with the result. 73| | 74| | - warning: This method mutates the query. It will reset the limit to `1`. 75| | 76| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 77| | - parameter completion: The block to execute. 78| | It should have the following argument signature: `^(ParseObject *object, ParseError *error)`. 79| | `result` will be `nil` if `error` is set OR no object was found matching the query. 80| | `error` will be `nil` if `result` is set OR if the query succeeded, but found no results. 81| | */ 82| 0| func first(callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { 83| 0| first(options: [], callbackQueue: callbackQueue, completion: completion) 84| 0| } 85| | 86| | /** 87| | Counts objects *asynchronously* and calls the given block with the counts. 88| | 89| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 90| | - parameter completion: The block to execute. 91| | It should have the following argument signature: `^(int count, ParseError *error)` 92| | */ 93| 0| func count(callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { 94| 0| count(options: [], callbackQueue: callbackQueue, completion: completion) 95| 0| } 96| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Objects/Protocols/Saveable.swift: 1| |// 2| |// Saveable.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-07-24. 6| |// Copyright © 2020 Parse. All rights reserved. 7| |// 8| | 9| |public protocol Saveable: Codable { 10| | associatedtype SavingType 11| | 12| | func save(options: API.Options) throws -> SavingType 13| | func save() throws -> SavingType 14| |} 15| | 16| |extension Saveable { 17| 0| public func save() throws -> SavingType { 18| 0| return try save(options: []) 19| 0| } 20| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse Types/ACL.swift: 1| |// 2| |// ACL.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-08-19. 6| |// Copyright © 2017 Parse. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |/** 12| | Objects that conform to the `ACL` protocol are used to control which users can access or modify a particular 13| | `ParseObject`. Each `ParseObject` can have its own `ACL`. You can grant read and write permissions 14| | separately to specific users, to groups of users that belong to roles, or you can grant permissions to 15| | "the public" so that, for example, any user could read a particular object but only a particular set of users 16| | could write to that object. 17| |*/ 18| |public struct ParseACL: Codable, Equatable { 19| | private static let publicScope = "*" 20| | private var acl: [String: [Access: Bool]]? 21| | 22| | /** 23| | An enum specifying read and write access controls. 24| | */ 25| | public enum Access: String, Codable, CodingKey { 26| | case read 27| | case write 28| | 29| 0| public init(from decoder: Decoder) throws { 30| 0| self = Access(rawValue: try decoder.singleValueContainer().decode(String.self))! 31| 0| } 32| | 33| 0| public func encode(to encoder: Encoder) throws { 34| 0| var container = encoder.singleValueContainer() 35| 0| try container.encode(rawValue) 36| 0| } 37| | } 38| | 39| 9| public init() {} 40| | 41| | /** 42| | Controls whether the public is allowed to read this object. 43| | */ 44| | public var publicRead: Bool { 45| 4| get { 46| 4| return get(ParseACL.publicScope, access: .read) 47| 4| } 48| 2| set { 49| 2| set(ParseACL.publicScope, access: .read, value: newValue) 50| 2| } 51| | } 52| | 53| | /** 54| | Controls whether the public is allowed to write this object. 55| | */ 56| | public var publicWrite: Bool { 57| 6| get { 58| 6| return get(ParseACL.publicScope, access: .write) 59| 6| } 60| 4| set { 61| 4| set(ParseACL.publicScope, access: .write, value: newValue) 62| 4| } 63| | } 64| | 65| | /** 66| | Returns true if a particular key has a specific access level. 67| | - parameter key: The `ParseObject.objectId` of the user for which to retrieve access. 68| | - parameter access: The type of access. 69| | - returns: `true` if the user with this `key` has *explicit* access, otherwise `false`. 70| | */ 71| 33| public func get(_ key: String, access: Access) -> Bool { 72| 33| guard let acl = acl else { // no acl, all open! 73| 7| return false 74| 26| } 75| 26| return acl[key]?[access] ?? false 76| 33| } 77| | 78| | /** 79| | Gets whether the given user id is *explicitly* allowed to read this object. 80| | Even if this returns `false`, the user may still be able to access it if `publicReadAccess` returns `true` 81| | or if the user belongs to a role that has access. 82| | 83| | - parameter userId: The `ParseObject.objectId` of the user for which to retrieve access. 84| | - returns: `true` if the user with this `objectId` has *explicit* read access, otherwise `false`. 85| | */ 86| 11| public func getReadAccess(userId: String) -> Bool { 87| 11| return get(userId, access: .read) 88| 11| } 89| | 90| | /** 91| | Gets whether the given user id is *explicitly* allowed to write this object. 92| | Even if this returns false, the user may still be able to write it if `publicWriteAccess` returns `true` 93| | or if the user belongs to a role that has access. 94| | 95| | - parameter userId: The `ParseObject.objectId` of the user for which to retrieve access. 96| | 97| | - returns: `true` if the user with this `ParseObject.objectId` has *explicit* write access, otherwise `false`. 98| | */ 99| 8| public func getWriteAccess(userId: String) -> Bool { 100| 8| return get(userId, access: .write) 101| 8| } 102| | 103| | /** 104| | Set whether the given `userId` is allowed to read this object. 105| | 106| | - parameter value: Whether the given user can write this object. 107| | - parameter userId: The `ParseObject.objectId` of the user to assign access. 108| | */ 109| 7| public mutating func setReadAccess(userId: String, value: Bool) { 110| 7| set(userId, access: .read, value: value) 111| 7| } 112| | 113| | /** 114| | Set whether the given `userId` is allowed to write this object. 115| | 116| | - parameter value: Whether the given user can read this object. 117| | - parameter userId: The `ParseObject.objectId` of the user to assign access. 118| | */ 119| 5| public mutating func setWriteAccess(userId: String, value: Bool) { 120| 5| set(userId, access: .write, value: value) 121| 5| } 122| | 123| | /** 124| | Get whether users belonging to the role with the given name are allowed to read this object. 125| | Even if this returns `false`, the role may still be able to read it if a parent role has read access. 126| | 127| | - parameter roleName: The name of the role. 128| | 129| | - returns: `true` if the role has read access, otherwise `false`. 130| | */ 131| 2| public func getReadAccess(roleName: String) -> Bool { 132| 2| return get(toRole(roleName: roleName), access: .read) 133| 2| } 134| | 135| | /** 136| | Get whether users belonging to the role with the given name are allowed to write this object. 137| | Even if this returns `false`, the role may still be able to write it if a parent role has write access. 138| | 139| | - parameter roleName: The name of the role. 140| | 141| | - returns: `true` if the role has read access, otherwise `false`. 142| | */ 143| 2| public func getWriteAccess(roleName: String) -> Bool { 144| 2| return get(toRole(roleName: roleName), access: .write) 145| 2| } 146| | 147| | /** 148| | Set whether users belonging to the role with the given name are allowed to read this object. 149| | 150| | - parameter value: Whether the given role can read this object. 151| | - parameter roleName: The name of the role. 152| | */ 153| 1| public mutating func setReadAccess(roleName: String, value: Bool) { 154| 1| set(toRole(roleName: roleName), access: .read, value: value) 155| 1| } 156| | 157| | /** 158| | Set whether users belonging to the role with the given name are allowed to write this object. 159| | 160| | - parameter allowed: Whether the given role can write this object. 161| | - parameter roleName: The name of the role. 162| | */ 163| 1| public mutating func setWriteAccess(roleName: String, value: Bool) { 164| 1| set(toRole(roleName: roleName), access: .write, value: value) 165| 1| } 166| | 167| 6| private func toRole(roleName: String) -> String { 168| 6| return "role:\(roleName)" 169| 6| } 170| | 171| 28| private mutating func set(_ key: String, access: Access, value: Bool) { 172| 28| // initialized the backing dictionary if needed 173| 28| if acl == nil && value { // do not create if value is false (no-op) 174| 12| acl = [:] 175| 28| } 176| 28| // initialize the scope dictionary 177| 28| if acl?[key] == nil && value { // do not create if value is false (no-op) 178| 20| acl?[key] = [:] 179| 28| } 180| 28| if value { 181| 26| acl?[key]?[access] = value 182| 28| } else { 183| 2| acl?[key]?.removeValue(forKey: access) 184| 2| if acl?[key]?.isEmpty == true { 185| 0| acl?.removeValue(forKey: key) 186| 2| } 187| 2| if acl?.isEmpty == true { 188| 0| acl = nil // cleanup 189| 2| } 190| 28| } 191| 28| } 192| |} 193| | 194| |// Default ACL 195| |extension ParseACL { 196| | /** 197| | Get the default ACL from the shared secure store. 198| | 199| | - returns: Returns the default ACL. 200| | */ 201| 5| public static func defaultACL() throws -> Self { 202| 5| 203| 5| let currentUser = BaseParseUser.current 204| 5| let aclController: DefaultACL? = 205| 5| try? ParseStorage.shared.secureStore.get(valueFor: ParseStorage.Keys.defaultACL) 206| 5| 207| 5| if aclController != nil { 208| 3| if !aclController!.useCurrentUser { 209| 1| return aclController!.defaultACL 210| 2| } else { 211| 2| guard let userObjectId = currentUser?.objectId else { 212| 1| return aclController!.defaultACL 213| 1| } 214| 1| 215| 1| guard let lastCurrentUserObjectId = aclController!.lastCurrentUser?.objectId, 216| 1| userObjectId == lastCurrentUserObjectId else { 217| 0| return try setDefaultACL(ParseACL(), withAccessForCurrentUser: true) 218| 1| } 219| 1| 220| 1| return aclController!.defaultACL 221| 1| } 222| 2| } 223| 2| 224| 2| return try setDefaultACL(ParseACL(), withAccessForCurrentUser: true) 225| 5| } 226| | 227| | /** 228| | Sets a default ACL that will be applied to all instances of `ParseObject` when they are created. 229| | 230| | - parameter acl: The ACL to use as a template for all instances of `ParseObject` created 231| | after this method has been called. 232| | 233| | This value will be copied and used as a template for the creation of new ACLs, so changes to the 234| | instance after this method has been called will not be reflected in new instance of `ParseObject`. 235| | 236| | - parameter withAccessForCurrentUser: If `true`, the `ParseACL` that is applied to 237| | newly-created instance of `ParseObject` will 238| | provide read and write access to the `ParseUser.+currentUser` at the time of creation. 239| | - If `false`, the provided `acl` will be used without modification. 240| | - If `acl` is `nil`, this value is ignored. 241| | 242| | - returns: Updated defaultACL 243| | */ 244| 6| public static func setDefaultACL(_ acl: ParseACL, withAccessForCurrentUser: Bool) throws -> ParseACL { 245| 6| 246| 6| let currentUser = BaseParseUser.current 247| 6| 248| 6| let modifiedACL: ParseACL? 249| 6| if withAccessForCurrentUser { 250| 5| modifiedACL = setDefaultAccess(acl) 251| 6| } else { 252| 1| modifiedACL = acl 253| 6| } 254| 6| 255| 6| let aclController: DefaultACL! 256| 6| if modifiedACL != nil { 257| 3| aclController = DefaultACL(defaultACL: modifiedACL!, 258| 3| lastCurrentUser: currentUser, useCurrentUser: withAccessForCurrentUser) 259| 6| } else { 260| 3| aclController = 261| 3| DefaultACL(defaultACL: acl, lastCurrentUser: currentUser, useCurrentUser: withAccessForCurrentUser) 262| 6| } 263| 6| 264| 6| try? ParseStorage.shared.secureStore.set(aclController, for: ParseStorage.Keys.defaultACL) 265| 6| 266| 6| return aclController.defaultACL 267| 6| } 268| | 269| 5| private static func setDefaultAccess(_ acl: ParseACL) -> ParseACL? { 270| 5| guard let userObjectId = BaseParseUser.current?.objectId else { 271| 3| return nil 272| 3| } 273| 2| var modifiedACL = acl 274| 2| modifiedACL.setReadAccess(userId: userObjectId, value: true) 275| 2| modifiedACL.setWriteAccess(userId: userObjectId, value: true) 276| 2| 277| 2| return modifiedACL 278| 5| } 279| |} 280| | 281| |// Encoding and decoding 282| |extension ParseACL { 283| 4| public init(from decoder: Decoder) throws { 284| 4| let container = try decoder.container(keyedBy: RawCodingKey.self) 285| 6| try container.allKeys.lazy.map { (scope) -> (String, KeyedDecodingContainer) in 286| 6| return (scope.stringValue, 287| 6| try container.nestedContainer(keyedBy: Access.self, forKey: scope)) 288| 6| }.flatMap { pair -> [(String, Access, Bool)] in 289| 6| let (scope, accessValues) = pair 290| 8| return try accessValues.allKeys.compactMap { (access) -> (String, Access, Bool)? in 291| 8| guard let value = try accessValues.decodeIfPresent(Bool.self, forKey: access) else { 292| 0| return nil 293| 8| } 294| 8| return (scope, access, value) 295| 8| } 296| 8| }.forEach { 297| 8| set($0, access: $1, value: $2) 298| 8| } 299| 4| } 300| | 301| 7| public func encode(to encoder: Encoder) throws { 302| 7| guard let acl = acl else { return } // only encode if acl is present 303| 5| var container = encoder.container(keyedBy: RawCodingKey.self) 304| 7| try acl.forEach { pair in 305| 7| let (scope, values) = pair 306| 7| var nestedContainer = container.nestedContainer(keyedBy: Access.self, 307| 7| forKey: .key(scope)) 308| 10| try values.forEach { (pair) in 309| 10| let (access, value) = pair 310| 10| try nestedContainer.encode(value, forKey: access) 311| 10| } 312| 7| } 313| 5| } 314| | 315| |} 316| | 317| |extension ParseACL: CustomDebugStringConvertible { 318| 0| public var debugDescription: String { 319| 0| guard let descriptionData = try? JSONEncoder().encode(self), 320| 0| let descriptionString = String(data: descriptionData, encoding: .utf8) else { 321| 0| return "ACL ()" 322| 0| } 323| 0| return "ACL (\(descriptionString))" 324| 0| } 325| |} 326| | 327| |struct DefaultACL: Codable { 328| | var defaultACL: ParseACL 329| | var lastCurrentUser: BaseParseUser? 330| | var useCurrentUser: Bool 331| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse Types/File.swift: 1| |import Foundation 2| | 3| |public struct File: Saveable, Fetchable { 4| | 5| | private let __type: String = "File" // swiftlint:disable:this identifier_name 6| | public var data: Data? 7| | public var url: URL? 8| | 9| 0| public init(data: Data?, url: URL?) { 10| 0| self.data = data 11| 0| self.url = url 12| 0| } 13| | 14| 0| public func save(options: API.Options) throws -> File { 15| 0| // upload file 16| 0| // store in server 17| 0| // callback with the data 18| 0| fatalError() 19| 0| } 20| | 21| 0| public func encode(to encoder: Encoder) throws { 22| 0| if data == nil && url == nil { 23| 0| throw ParseError(code: .unknownError, message: "cannot encode file") 24| 0| } 25| 0| var container = encoder.container(keyedBy: CodingKeys.self) 26| 0| if let url = url { 27| 0| try container.encode(__type, forKey: .__type) 28| 0| try container.encode(url.absoluteString, forKey: .url) 29| 0| } 30| 0| if let data = data { 31| 0| try container.encode(__type, forKey: .__type) 32| 0| try container.encode(data, forKey: .data) 33| 0| } 34| 0| } 35| | 36| 0| public func fetch(options: API.Options) -> File { 37| 0| fatalError() 38| 0| } 39| | 40| | enum CodingKeys: String, CodingKey { 41| | case url 42| | case data 43| | case __type // swiftlint:disable:this identifier_name 44| | } 45| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse Types/GeoPoint.swift: 1| |import Foundation 2| | 3| |public struct GeoPoint: Codable { 4| | private let __type: String = "GeoPoint" // swiftlint:disable:this identifier_name 5| | public let latitude: Double 6| | public let longitude: Double 7| 0| public init(latitude: Double, longitude: Double) { 8| 0| assert(latitude > -180, "latitude should be > -180") 9| 0| assert(latitude < 180, "latitude should be > -180") 10| 0| assert(latitude > -90, "latitude should be > -90") 11| 0| assert(latitude < 90, "latitude should be > 90") 12| 0| self.latitude = latitude 13| 0| self.longitude = longitude 14| 0| } 15| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse Types/Internal/BaseParseInstallation.swift: 1| |// 2| |// BaseParseInstallation.swift 3| |// ParseSwift 4| |// 5| |// Created by Corey Baker on 9/7/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |internal struct BaseParseInstallation: ParseInstallation { 12| | var deviceType: String? 13| | var installationId: String? 14| | var deviceToken: String? 15| | var badge: Int? 16| | var timeZone: String? 17| | var channels: [String]? 18| | var appName: String? 19| | var appIdentifier: String? 20| | var appVersion: String? 21| | var parseVersion: String? 22| | var localeIdentifier: String? 23| | var objectId: String? 24| | var createdAt: Date? 25| | var updatedAt: Date? 26| | var ACL: ParseACL? 27| | 28| 73| init() { 29| 73| //Force installation in secure store to be created if it hasn't already 30| 73| Self.current = self 31| 73| } 32| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse Types/ParseError.swift: 1| |// 2| |// ParseError.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-09-24. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |public struct ParseError: Swift.Error, Codable { 12| | let code: Code 13| | let message: String 14| | 15| 0| var localizedDescription: String { 16| 0| return "ParseError code=\(code.rawValue) error=\(message)" 17| 0| } 18| | 19| | enum CodingKeys: String, CodingKey { 20| | case code 21| | case message = "error" 22| | } 23| | 24| | /** 25| | `ParseError.Code` enum contains all custom error codes that are used 26| | as `code` for `Error` for callbacks on all classes. 27| | */ 28| | enum Code: Int, Swift.Error, Codable { 29| | /** 30| | Internal SDK Error. No information available 31| | */ 32| | case unknownError = -1 33| | 34| | /** 35| | Internal server error. No information available. 36| | */ 37| | case internalServer = 1 38| | /** 39| | The connection to the Parse servers failed. 40| | */ 41| | case connectionFailed = 100 42| | /** 43| | Object doesn't exist, or has an incorrect password. 44| | */ 45| | case objectNotFound = 101 46| | /** 47| | You tried to find values matching a datatype that doesn't 48| | support exact database matching, like an array or a dictionary. 49| | */ 50| | case invalidQuery = 102 51| | /** 52| | Missing or invalid classname. Classnames are case-sensitive. 53| | They must start with a letter, and `a-zA-Z0-9_` are the only valid characters. 54| | */ 55| | case invalidClassName = 103 56| | /** 57| | Missing object id. 58| | */ 59| | case missingObjectId = 104 60| | /** 61| | Invalid key name. Keys are case-sensitive. 62| | They must start with a letter, and `a-zA-Z0-9_` are the only valid characters. 63| | */ 64| | case invalidKeyName = 105 65| | /** 66| | Malformed pointer. Pointers must be arrays of a classname and an object id. 67| | */ 68| | case invalidPointer = 106 69| | /** 70| | Malformed json object. A json dictionary is expected. 71| | */ 72| | case invalidJSON = 107 73| | /** 74| | Tried to access a feature only available internally. 75| | */ 76| | case commandUnavailable = 108 77| | /** 78| | Field set to incorrect type. 79| | */ 80| | case incorrectType = 111 81| | /** 82| | Invalid channel name. A channel name is either an empty string (the broadcast channel) 83| | or contains only `a-zA-Z0-9_` characters and starts with a letter. 84| | */ 85| | case invalidChannelName = 112 86| | /** 87| | Invalid device token. 88| | */ 89| | case invalidDeviceToken = 114 90| | /** 91| | Push is misconfigured. See details to find out how. 92| | */ 93| | case pushMisconfigured = 115 94| | /** 95| | The object is too large. 96| | */ 97| | case objectTooLarge = 116 98| | /** 99| | That operation isn't allowed for clients. 100| | */ 101| | case operationForbidden = 119 102| | /** 103| | The results were not found in the cache. 104| | */ 105| | case cacheMiss = 120 106| | /** 107| | Keys in `NSDictionary` values may not include `$` or `.`. 108| | */ 109| | case invalidNestedKey = 121 110| | /** 111| | Invalid file name. 112| | A file name can contain only `a-zA-Z0-9_.` characters and should be between 1 and 36 characters. 113| | */ 114| | case invalidFileName = 122 115| | /** 116| | Invalid ACL. An ACL with an invalid format was saved. This should not happen if you use `ParseACL`. 117| | */ 118| | case invalidACL = 123 119| | /** 120| | The request timed out on the server. Typically this indicates the request is too expensive. 121| | */ 122| | case timeout = 124 123| | /** 124| | The email address was invalid. 125| | */ 126| | case invalidEmailAddress = 125 127| | /** 128| | A unique field was given a value that is already taken. 129| | */ 130| | case duplicateValue = 137 131| | /** 132| | Role's name is invalid. 133| | */ 134| | case invalidRoleName = 139 135| | /** 136| | Exceeded an application quota. Upgrade to resolve. 137| | */ 138| | case exceededQuota = 140 139| | /** 140| | Cloud Code script had an error. 141| | */ 142| | case scriptError = 141 143| | /** 144| | Cloud Code validation failed. 145| | */ 146| | case validationError = 142 147| | /** 148| | Product purchase receipt is missing. 149| | */ 150| | case receiptMissing = 143 151| | /** 152| | Product purchase receipt is invalid. 153| | */ 154| | case invalidPurchaseReceipt = 144 155| | /** 156| | Payment is disabled on this device. 157| | */ 158| | case paymentDisabled = 145 159| | /** 160| | The product identifier is invalid. 161| | */ 162| | case invalidProductIdentifier = 146 163| | /** 164| | The product is not found in the App Store. 165| | */ 166| | case productNotFoundInAppStore = 147 167| | /** 168| | The Apple server response is not valid. 169| | */ 170| | case invalidServerResponse = 148 171| | /** 172| | Product fails to download due to file system error. 173| | */ 174| | case productDownloadFileSystemFailure = 149 175| | /** 176| | Fail to convert data to image. 177| | */ 178| | case invalidImageData = 150 179| | /** 180| | Unsaved file. 181| | */ 182| | case unsavedFile = 151 183| | /** 184| | Fail to delete file. 185| | */ 186| | case fileDeleteFailure = 153 187| | /** 188| | Application has exceeded its request limit. 189| | */ 190| | case requestLimitExceeded = 155 191| | /** 192| | Invalid event name. 193| | */ 194| | case invalidEventName = 160 195| | /** 196| | Username is missing or empty. 197| | */ 198| | case usernameMissing = 200 199| | /** 200| | Password is missing or empty. 201| | */ 202| | case userPasswordMissing = 201 203| | /** 204| | Username has already been taken. 205| | */ 206| | case usernameTaken = 202 207| | /** 208| | Email has already been taken. 209| | */ 210| | case userEmailTaken = 203 211| | /** 212| | The email is missing, and must be specified. 213| | */ 214| | case userEmailMissing = 204 215| | /** 216| | A user with the specified email was not found. 217| | */ 218| | case userWithEmailNotFound = 205 219| | /** 220| | The user cannot be altered by a client without the session. 221| | */ 222| | case userCannotBeAlteredWithoutSession = 206 223| | /** 224| | Users can only be created through sign up. 225| | */ 226| | case userCanOnlyBeCreatedThroughSignUp = 207 227| | 228| | /** 229| | An existing account already linked to another user. 230| | */ 231| | case accountAlreadyLinked = 208 232| | /** 233| | Error code indicating that the current session token is invalid. 234| | */ 235| | case invalidSessionToken = 209 236| | 237| | /** 238| | Linked id missing from request. 239| | */ 240| | case linkedIdMissing = 250 241| | 242| | /** 243| | Invalid linked session. 244| | */ 245| | case invalidLinkedSession = 251 246| | 247| | /** 248| | Error code indicating that a service being linked (e.g. Facebook or 249| | Twitter) is unsupported. 250| | */ 251| | case unsupportedService = 252 252| | 253| | /** 254| | Error code indicating an invalid operation occured on schema 255| | */ 256| | case invalidSchemaOperation = 255 257| | 258| | /** 259| | Error code indicating that there were multiple errors. Aggregate errors 260| | have an "errors" property, which is an array of error objects with more 261| | detail about each error that occurred. 262| | */ 263| | case aggregateError = 600 264| | 265| | /** 266| | Error code indicating the client was unable to read an input file. 267| | */ 268| | case fileReadError = 601 269| | 270| | /** 271| | Error code indicating a real error code is unavailable because 272| | we had to use an XDomainRequest object to allow CORS requests in 273| | Internet Explorer, which strips the body from HTTP responses that have 274| | a non-2XX status code. 275| | */ 276| | case xDomainRequest = 602 277| | } 278| |} 279| | 280| |extension ParseError { 281| | 282| 2| public init(from decoder: Decoder) throws { 283| 2| let values = try decoder.container(keyedBy: CodingKeys.self) 284| 2| code = try values.decode(Code.self, forKey: .code) 285| 2| message = try values.decode(String.self, forKey: .message) 286| 2| } 287| | 288| 0| public func encode(to encoder: Encoder) throws { 289| 0| var container = encoder.container(keyedBy: CodingKeys.self) 290| 0| try container.encode(code, forKey: .code) 291| 0| try container.encode(message, forKey: .message) 292| 0| } 293| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse Types/Pointer.swift: 1| |import Foundation 2| | 3| 0|private func getObjectId(target: T) -> String { 4| 0| guard let objectId = target.objectId else { 5| 0| fatalError("Cannot set a pointer to an unsaved object") 6| 0| } 7| 0| return objectId 8| 0|} 9| | 10| |public struct Pointer: Fetchable, Codable { 11| | public typealias FetchingType = T 12| | 13| | private let __type: String = "Pointer" // swiftlint:disable:this identifier_name 14| | public var objectId: String 15| | public var className: String 16| | 17| 0| public init(_ target: T) { 18| 0| self.objectId = getObjectId(target: target) 19| 0| self.className = target.className 20| 0| } 21| | 22| 0| public init(objectId: String) { 23| 0| self.className = T.className 24| 0| self.objectId = objectId 25| 0| } 26| | 27| | private enum CodingKeys: String, CodingKey { 28| | case __type, objectId, className // swiftlint:disable:this identifier_name 29| | } 30| |} 31| | 32| |extension Pointer { 33| 0| public func fetch(options: API.Options = []) throws -> T { 34| 0| let path = API.Endpoint.object(className: className, objectId: objectId) 35| 0| return try API.Command(method: .GET, 36| 0| path: path) { (data) -> T in 37| 0| try ParseCoding.jsonDecoder().decode(T.self, from: data) 38| 0| }.execute(options: options) 39| 0| } 40| | 41| | public func fetch(options: API.Options = [], callbackQueue: DispatchQueue = .main, 42| 0| completion: @escaping (Result) -> Void) { 43| 0| let path = API.Endpoint.object(className: className, objectId: objectId) 44| 0| API.Command(method: .GET, 45| 0| path: path) { (data) -> T in 46| 0| try ParseCoding.jsonDecoder().decode(T.self, from: data) 47| 0| }.executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) 48| 0| } 49| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse Types/Query.swift: 1| |// 2| |// Query.swift 3| |// Parse 4| |// 5| |// Created by Florent Vilmart on 17-07-23. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |/** 12| | All available query constraints. 13| |*/ 14| |public struct QueryConstraint: Encodable { 15| | public enum Comparator: String, CodingKey { 16| | case lessThan = "$lt" 17| | case lessThanOrEqualTo = "$lte" 18| | case greaterThan = "$gt" 19| | case greaterThanOrEqualTo = "$gte" 20| | case equals = "$eq" 21| | case notEqualTo = "$ne" 22| | case containedIn = "$in" 23| | case notContainedIn = "$nin" 24| | case containedBy = "$containedBy" 25| | case exists = "$exists" 26| | case select = "$select" 27| | case dontSelect = "$dontSelect" 28| | case all = "$all" 29| | case regex = "$regex" 30| | case inQuery = "$inQuery" 31| | case notInQuery = "$notInQuery" 32| | } 33| | 34| | var key: String 35| | var value: Encodable 36| | var comparator: Comparator 37| | 38| 0| public func encode(to encoder: Encoder) throws { 39| 0| if let value = value as? Date { 40| 0| // Special case for date... Not sure why encoder don't like 41| 0| try value.parseRepresentation.encode(to: encoder) 42| 0| } else { 43| 0| try value.encode(to: encoder) 44| 0| } 45| 0| } 46| |} 47| | 48| 4|public func > (key: String, value: T) -> QueryConstraint where T: Encodable { 49| 4| return QueryConstraint(key: key, value: value, comparator: .greaterThan) 50| 4|} 51| | 52| 0|public func >= (key: String, value: T) -> QueryConstraint where T: Encodable { 53| 0| return QueryConstraint(key: key, value: value, comparator: .greaterThanOrEqualTo) 54| 0|} 55| | 56| 0|public func < (key: String, value: T) -> QueryConstraint where T: Encodable { 57| 0| return QueryConstraint(key: key, value: value, comparator: .lessThan) 58| 0|} 59| | 60| 0|public func <= (key: String, value: T) -> QueryConstraint where T: Encodable { 61| 0| return QueryConstraint(key: key, value: value, comparator: .lessThanOrEqualTo) 62| 0|} 63| | 64| 0|public func == (key: String, value: T) -> QueryConstraint where T: Encodable { 65| 0| return QueryConstraint(key: key, value: value, comparator: .equals) 66| 0|} 67| | 68| |private struct InQuery: Encodable where T: ParseObject { 69| | let query: Query 70| 0| var className: String { 71| 0| return T.className 72| 0| } 73| | 74| 0| func encode(to encoder: Encoder) throws { 75| 0| var container = encoder.container(keyedBy: CodingKeys.self) 76| 0| try container.encode(className, forKey: .className) 77| 0| try container.encode(query.where, forKey: .where) 78| 0| } 79| | 80| | enum CodingKeys: String, CodingKey { 81| | case `where`, className 82| | } 83| |} 84| | 85| 0|public func == (key: String, value: Query) -> QueryConstraint { 86| 0| return QueryConstraint(key: key, value: InQuery(query: value), comparator: .inQuery) 87| 0|} 88| | 89| |internal struct QueryWhere: Encodable { 90| 310| private var _constraints = [String: [QueryConstraint]]() 91| | 92| 4| mutating func add(_ constraint: QueryConstraint) { 93| 4| var existing = _constraints[constraint.key] ?? [] 94| 4| existing.append(constraint) 95| 4| _constraints[constraint.key] = existing 96| 4| } 97| | 98| | // This only encodes the where... 99| 306| func encode(to encoder: Encoder) throws { 100| 306| var container = encoder.container(keyedBy: RawCodingKey.self) 101| 306| try _constraints.forEach { (key, value) in 102| 0| var nestedContainer = container.nestedContainer(keyedBy: QueryConstraint.Comparator.self, 103| 0| forKey: .key(key)) 104| 0| try value.forEach { (constraint) in 105| 0| try constraint.encode(to: nestedContainer.superEncoder(forKey: constraint.comparator)) 106| 0| } 107| 0| } 108| 306| } 109| |} 110| | 111| |/** 112| |The `ParseQuery` struct defines a query that is used to query for `ParseObject`s. 113| |*/ 114| |public struct Query: Encodable where T: ParseObject { 115| | // interpolate as GET 116| | private let method: String = "GET" 117| | private var limit: Int = 100 118| | private var skip: Int = 0 119| | private var keys: [String]? 120| | private var include: [String]? 121| | private var order: [Order]? 122| | private var isCount: Bool? 123| 310| private var explain: Bool? = false 124| | private var hint: AnyCodable? 125| | 126| 310| fileprivate var `where` = QueryWhere() 127| | 128| | /** 129| | An enum that determines the order to sort the results based on a given key. 130| | 131| | - parameter key: The key to order by. 132| | */ 133| | public enum Order: Encodable { 134| | case ascending(String) 135| | case descending(String) 136| | 137| 0| public func encode(to encoder: Encoder) throws { 138| 0| var container = encoder.singleValueContainer() 139| 0| switch self { 140| 0| case .ascending(let value): 141| 0| try container.encode(value) 142| 0| case .descending(let value): 143| 0| try container.encode("-\(value)") 144| 0| } 145| 0| } 146| | } 147| | 148| 308| public init(_ constraints: QueryConstraint...) { 149| 308| self.init(constraints) 150| 308| } 151| | 152| 310| public init(_ constraints: [QueryConstraint]) { 153| 310| constraints.forEach({ self.where.add($0) }) 154| 310| } 155| | 156| 0| public mutating func `where`(_ constraints: QueryConstraint...) -> Query { 157| 0| constraints.forEach({ self.where.add($0) }) 158| 0| return self 159| 0| } 160| | 161| | /** 162| | A limit on the number of objects to return. The default limit is `100`, with a 163| | maximum of 1000 results being returned at a time. 164| | 165| | - note: If you are calling `findObjects` with `limit = 1`, you may find it easier to use `first` instead. 166| | */ 167| 0| public mutating func limit(_ value: Int) -> Query { 168| 0| self.limit = value 169| 0| return self 170| 0| } 171| | 172| | /** 173| | The number of objects to skip before returning any. 174| | */ 175| 0| public mutating func skip(_ value: Int) -> Query { 176| 0| self.skip = value 177| 0| return self 178| 0| } 179| | 180| | /** 181| | Investigates the query execution plan. Useful for optimizing queries. 182| | */ 183| 0| public mutating func explain(_ value: Bool) -> Query { 184| 0| self.explain = value 185| 0| return self 186| 0| } 187| | 188| | /** 189| | Investigates the query execution plan. Useful for optimizing queries. 190| | */ 191| 0| public mutating func hint(_ value: AnyCodable) -> Query { 192| 0| self.hint = value 193| 0| return self 194| 0| } 195| | 196| | /** 197| | The className of a `ParseObject` to query. 198| | */ 199| 316| var className: String { 200| 316| return T.className 201| 316| } 202| | 203| | /** 204| | The className of a `ParseObject` to query. 205| | */ 206| 0| static var className: String { 207| 0| return T.className 208| 0| } 209| | 210| 306| var endpoint: API.Endpoint { 211| 306| return .objects(className: className) 212| 306| } 213| | 214| | enum CodingKeys: String, CodingKey { 215| | case `where` 216| | case method = "_method" 217| | case limit 218| | case skip 219| | case isCount = "count" 220| | case keys 221| | case order 222| | case explain 223| | case hint 224| | } 225| |} 226| | 227| |extension Query: Queryable { 228| | 229| | public typealias ResultType = T 230| | 231| | /** 232| | Finds objects *synchronously* based on the constructed query and sets an error if there was one. 233| | 234| | - parameter options: A set of options used to save objects. 235| | - throws: An error of type `ParseError`. 236| | 237| | - returns: Returns an array of `ParseObject`s that were found. 238| | */ 239| 1| public func find(options: API.Options = []) throws -> [ResultType] { 240| 1| let foundResults = try findCommand().execute(options: options) 241| 1| try? ResultType.updateSecureStorageIfNeeded(foundResults) 242| 1| return foundResults 243| 1| } 244| | 245| | /** 246| | Finds objects *asynchronously* and calls the given block with the results. 247| | 248| | - parameter options: A set of options used to save objects. 249| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 250| | - parameter completion: The block to execute. 251| | It should have the following argument signature: `(Result<[ResultType], ParseError>)` 252| | */ 253| | public func find(options: API.Options = [], callbackQueue: DispatchQueue, 254| 101| completion: @escaping (Result<[ResultType], ParseError>) -> Void) { 255| 101| findCommand().executeAsync(options: options, callbackQueue: callbackQueue) { results in 256| 101| if case .success(let foundResults) = results { 257| 101| try? ResultType.updateSecureStorageIfNeeded(foundResults) 258| 101| } 259| 101| completion(results) 260| 101| } 261| 101| } 262| | 263| | /** 264| | Gets an object *synchronously* based on the constructed query and sets an error if any occurred. 265| | 266| | - warning: This method mutates the query. It will reset the limit to `1`. 267| | 268| | - parameter options: A set of options used to save objects. 269| | - throws: An error of type `ParseError`. 270| | 271| | - returns: Returns a `ParseObject`, or `nil` if none was found. 272| | */ 273| 1| public func first(options: API.Options = []) throws -> ResultType? { 274| 1| let result = try firstCommand().execute(options: options) 275| 1| if let foundResult = result { 276| 1| try? ResultType.updateSecureStorageIfNeeded([foundResult]) 277| 1| } 278| 1| return result 279| 1| } 280| | 281| | /** 282| | Gets an object *asynchronously* and calls the given block with the result. 283| | 284| | - warning: This method mutates the query. It will reset the limit to `1`. 285| | 286| | - parameter options: A set of options used to save objects. 287| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 288| | - parameter completion: The block to execute. 289| | It should have the following argument signature: `^(ParseObject *object, ParseError *error)`. 290| | `result` will be `nil` if `error` is set OR no object was found matching the query. 291| | `error` will be `nil` if `result` is set OR if the query succeeded, but found no results. 292| | */ 293| | public func first(options: API.Options = [], callbackQueue: DispatchQueue, 294| 101| completion: @escaping (Result) -> Void) { 295| 101| firstCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in 296| 101| 297| 101| switch result { 298| 101| case .success(let first): 299| 101| guard let first = first else { 300| 0| completion(.failure(ParseError(code: .unknownError, message: "unable to unwrap data") )) 301| 0| return 302| 101| } 303| 101| try? ResultType.updateSecureStorageIfNeeded([first]) 304| 101| completion(.success(first)) 305| 101| case .failure(let error): 306| 0| completion(.failure(error)) 307| 101| } 308| 101| } 309| 101| } 310| | 311| | /** 312| | Counts objects *synchronously* based on the constructed query and sets an error if there was one. 313| | 314| | - parameter options: A set of options used to save objects. 315| | - throws: An error of type `ParseError`. 316| | 317| | - returns: Returns the number of `ParseObject`s that match the query, or `-1` if there is an error. 318| | */ 319| 1| public func count(options: API.Options = []) throws -> Int { 320| 1| return try countCommand().execute(options: options) 321| 1| } 322| | 323| | /** 324| | Counts objects *asynchronously* and calls the given block with the counts. 325| | 326| | - parameter options: A set of options used to save objects. 327| | - parameter callbackQueue: The queue to return to after completion. Default value of .main. 328| | - parameter completion: The block to execute. 329| | It should have the following argument signature: `^(int count, ParseError *error)` 330| | */ 331| | public func count(options: API.Options = [], callbackQueue: DispatchQueue, 332| 101| completion: @escaping (Result) -> Void) { 333| 101| countCommand().executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) 334| 101| } 335| |} 336| | 337| |private extension Query { 338| 102| private func findCommand() -> API.Command, [ResultType]> { 339| 102| return API.Command(method: .POST, path: endpoint, body: self) { 340| 102| try ParseCoding.jsonDecoder().decode(FindResult.self, from: $0).results 341| 102| } 342| 102| } 343| | 344| 102| private func firstCommand() -> API.Command, ResultType?> { 345| 102| var query = self 346| 102| query.limit = 1 347| 102| return API.Command(method: .POST, path: endpoint, body: query) { 348| 102| try ParseCoding.jsonDecoder().decode(FindResult.self, from: $0).results.first 349| 102| } 350| 102| } 351| | 352| 102| private func countCommand() -> API.Command, Int> { 353| 102| var query = self 354| 102| query.limit = 1 355| 102| query.isCount = true 356| 102| return API.Command(method: .POST, path: endpoint, body: query) { 357| 102| try ParseCoding.jsonDecoder().decode(FindResult.self, from: $0).count ?? 0 358| 102| } 359| 102| } 360| |} 361| | 362| |enum RawCodingKey: CodingKey { 363| | case key(String) 364| 19| var stringValue: String { 365| 19| switch self { 366| 19| case .key(let str): 367| 19| return str 368| 19| } 369| 19| } 370| 0| var intValue: Int? { 371| 0| fatalError() 372| 0| } 373| 6| init?(stringValue: String) { 374| 6| self = .key(stringValue) 375| 6| } 376| 0| init?(intValue: Int) { 377| 0| fatalError() 378| 0| } 379| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Parse.swift: 1| |import Foundation 2| | 3| |internal struct ParseConfiguration { 4| | static var applicationId: String! 5| | static var masterKey: String? 6| | static var clientKey: String? 7| | static var serverURL: URL! 8| | static var mountPath: String! 9| |} 10| | 11| |public func initialize( 12| | applicationId: String, 13| | clientKey: String? = nil, 14| | masterKey: String? = nil, 15| | serverURL: URL, 16| | primitiveObjectStore: PrimitiveObjectStore? = nil, 17| | secureStore: PrimitiveObjectStore? = nil 18| 73|) { 19| 73| ParseConfiguration.applicationId = applicationId 20| 73| ParseConfiguration.clientKey = clientKey 21| 73| ParseConfiguration.masterKey = masterKey 22| 73| ParseConfiguration.serverURL = serverURL 23| 73| ParseConfiguration.mountPath = "/" + serverURL.pathComponents 24| 146| .filter { $0 != "/" } 25| 73| .joined(separator: "/") 26| 73| 27| 73| ParseStorage.shared.secureStore = secureStore ?? KeychainStore.shared 28| 73| ParseStorage.shared.primitiveStore = primitiveObjectStore ?? CodableInMemoryPrimitiveObjectStore() 29| 73| 30| 73| DispatchQueue.main.async { 31| 73| _ = BaseParseInstallation() 32| 73| } 33| 73|} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Storage/KeychainStore.swift: 1| |// 2| |// KeychainStore.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-09-25. 6| |// Copyright © 2017 Parse. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| 16|func getKeychainQueryTemplate(forService service: String) -> [String: String] { 12| 16| var query = [String: String]() 13| 16| if service.count > 0 { 14| 16| query[kSecAttrService as String] = service 15| 16| } 16| 16| query[kSecClass as String] = kSecClassGenericPassword as String 17| 16| query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String 18| 16| return query 19| 16|} 20| | 21| |struct KeychainStore: SecureStorage { 22| | private let synchronizationQueue: DispatchQueue 23| | private let keychainQueryTemplate: [String: String] 24| | 25| | public static var shared = KeychainStore(service: "shared") 26| | 27| 16| init(service: String) { 28| 16| synchronizationQueue = DispatchQueue(label: "com.parse.keychain.\(service)", 29| 16| qos: .default, 30| 16| attributes: .concurrent, 31| 16| autoreleaseFrequency: .inherit, 32| 16| target: nil) 33| 16| keychainQueryTemplate = getKeychainQueryTemplate(forService: service) 34| 16| } 35| | 36| 7.89k| private func keychainQuery(forKey key: String) -> [String: Any] { 37| 7.89k| var query: [String: Any] = keychainQueryTemplate 38| 7.89k| query[kSecAttrAccount as String] = key 39| 7.89k| return query 40| 7.89k| } 41| | 42| 5.68k| func object(forKey key: String) -> T? where T: Decodable { 43| 5.68k| guard let data = synchronizationQueue.sync(execute: { () -> Data? in 44| 5.68k| return self.data(forKey: key) 45| 5.68k| }) else { 46| 3.08k| return nil 47| 3.08k| } 48| 2.59k| do { 49| 2.59k| let object = try JSONDecoder().decode(T.self, from: data) 50| 2.59k| return object 51| 2.59k| } catch { 52| 2| return nil 53| 18.4E| } 54| 18.4E| } 55| | 56| 983| func set(object: T?, forKey key: String) -> Bool where T: Encodable { 57| 983| guard let object = object else { 58| 1| return removeObject(forKey: key) 59| 982| } 60| 982| do { 61| 982| let data = try JSONEncoder().encode(object) 62| 982| let query = keychainQuery(forKey: key) 63| 982| let update = [ 64| 982| kSecValueData as String: data 65| 982| ] 66| 982| 67| 982| let status = synchronizationQueue.sync(flags: .barrier) { () -> OSStatus in 68| 982| if self.data(forKey: key) != nil { 69| 729| return SecItemUpdate(query as CFDictionary, update as CFDictionary) 70| 729| } 71| 253| let mergedQuery = query.merging(update) { (_, otherValue) -> Any in otherValue } 72| 253| return SecItemAdd(mergedQuery as CFDictionary, nil) 73| 982| } 74| 982| 75| 982| return status == errSecSuccess 76| 982| } catch { 77| 0| return false 78| 0| } 79| 0| } 80| | 81| | subscript(key: String) -> T? where T: Codable { 82| 6| get { 83| 6| return object(forKey: key) 84| 6| } 85| 11| set (object) { 86| 11| _ = set(object: object, forKey: key) 87| 11| } 88| | } 89| | 90| 104| func removeObject(forKey key: String) -> Bool { 91| 104| return synchronizationQueue.sync { 92| 104| return removeObject(forKeyUnsafe: key) 93| 104| } 94| 104| } 95| | 96| 189| func removeAllObjects() -> Bool { 97| 189| var query = keychainQueryTemplate as [String: Any] 98| 189| query[kSecReturnAttributes as String] = kCFBooleanTrue 99| 189| query[kSecMatchLimit as String] = kSecMatchLimitAll 100| 189| 101| 189| return synchronizationQueue.sync(flags: .barrier) { () -> Bool in 102| 189| var result: AnyObject? 103| 189| let status = withUnsafeMutablePointer(to: &result) { 104| 189| SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) 105| 189| } 106| 189| if status != errSecSuccess { return true } 107| 98| 108| 98| guard let results = result as? [[String: Any]] else { return false } 109| 98| 110| 148| for dict in results { 111| 148| guard let key = dict[kSecAttrAccount as String] as? String, 112| 148| self.removeObject(forKeyUnsafe: key) else { 113| 0| return false 114| 148| } 115| 148| } 116| 98| return true 117| 98| } 118| 189| } 119| | 120| 6.66k| private func data(forKey key: String) -> Data? { 121| 6.66k| var query: [String: Any] = keychainQuery(forKey: key) 122| 6.66k| query[kSecMatchLimit as String] = kSecMatchLimitOne 123| 6.66k| query[kSecReturnData as String] = kCFBooleanTrue 124| 6.66k| 125| 6.66k| var result: AnyObject? 126| 6.66k| let status = withUnsafeMutablePointer(to: &result) { 127| 6.66k| SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) 128| 6.66k| } 129| 6.66k| 130| 6.66k| guard status == errSecSuccess, 131| 6.66k| let data = result as? Data else { 132| 3.34k| return nil 133| 3.34k| } 134| 3.31k| 135| 3.31k| return data 136| 6.66k| } 137| | 138| 252| private func removeObject(forKeyUnsafe key: String) -> Bool { 139| 252| dispatchPrecondition(condition: .onQueue(synchronizationQueue)) 140| 252| return SecItemDelete(keychainQuery(forKey: key) as CFDictionary) == errSecSuccess 141| 252| } 142| |} 143| | 144| |extension KeychainStore /* TypedSubscript */ { 145| | subscript(string key: String) -> String? { 146| 6| get { 147| 6| return object(forKey: key) 148| 6| } 149| 1| set (object) { 150| 1| _ = set(object: object, forKey: key) 151| 1| } 152| | } 153| | 154| | subscript(bool key: String) -> Bool? { 155| 2| get { 156| 2| return object(forKey: key) 157| 2| } 158| 1| set (object) { 159| 1| _ = set(object: object, forKey: key) 160| 1| } 161| | } 162| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Storage/ParseStorage.swift: 1| |// 2| |// ParseStorage.swift 3| |// 4| |// 5| |// Created by Pranjal Satija on 7/19/20. 6| |// 7| | 8| |// MARK: ParseStorage 9| |public struct ParseStorage { 10| | public static var shared = ParseStorage() 11| | 12| 1| var primitiveStore: PrimitiveObjectStore = CodableInMemoryPrimitiveObjectStore() 13| 1| var secureStore: PrimitiveObjectStore = KeychainStore.shared 14| | 15| | enum Keys { 16| | static let currentUser = "_currentUser" 17| | static let currentInstallation = "_currentInstallation" 18| | static let defaultACL = "_defaultACL" 19| | } 20| |} /Users/runner/work/Parse-Swift/Parse-Swift/Sources/ParseSwift/Storage/PrimitiveObjectStore.swift: 1| |// 2| |// PrimitiveObjectStore.swift 3| |// 4| |// 5| |// Created by Pranjal Satija on 7/19/20. 6| |// 7| | 8| |import Foundation 9| | 10| |// MARK: PrimitiveObjectStore 11| |public protocol PrimitiveObjectStore { 12| | mutating func delete(valueFor key: String) throws 13| | mutating func deleteAll() throws 14| | mutating func get(valueFor key: String) throws -> T? 15| | mutating func set(_ object: T, for key: String) throws 16| |} 17| | 18| |/// A `PrimitiveObjectStore` that lives in memory for unit testing purposes. 19| |/// It works by encoding / decoding all values just like a real `Codable` store would 20| |/// but it stores all values as `Data` blobs in memory. 21| |struct CodableInMemoryPrimitiveObjectStore: PrimitiveObjectStore { 22| 74| var decoder = JSONDecoder() 23| 74| var encoder = JSONEncoder() 24| 74| var storage = [String: Data]() 25| | 26| 0| mutating func delete(valueFor key: String) throws { 27| 0| storage[key] = nil 28| 0| } 29| | 30| 0| mutating func deleteAll() throws { 31| 0| storage.removeAll() 32| 0| } 33| | 34| 0| mutating func get(valueFor key: String) throws -> T? where T: Decodable { 35| 0| guard let data = storage[key] else { return nil } 36| 0| return try decoder.decode(T.self, from: data) 37| 0| } 38| | 39| 0| mutating func set(_ object: T, for key: String) throws where T: Encodable { 40| 0| let data = try encoder.encode(object) 41| 0| storage[key] = data 42| 0| } 43| |} 44| | 45| |// MARK: KeychainStore + PrimitiveObjectStore 46| |extension KeychainStore: PrimitiveObjectStore { 47| | 48| 2| func delete(valueFor key: String) throws { 49| 2| if !removeObject(forKey: key) { 50| 0| throw ParseError(code: .objectNotFound, message: "Object for key \"\(key)\" not found in Keychain") 51| 2| } 52| 2| } 53| | 54| 73| func deleteAll() throws { 55| 73| if !removeAllObjects() { 56| 0| throw ParseError(code: .objectNotFound, message: "Couldn't delete all objects in Keychain") 57| 73| } 58| 73| } 59| | 60| 5.67k| func get(valueFor key: String) throws -> T? where T: Decodable { 61| 5.67k| object(forKey: key) 62| 5.67k| } 63| | 64| 569| func set(_ object: T, for key: String) throws where T: Encodable { 65| 569| if !set(object: object, forKey: key) { 66| 0| throw ParseError(code: .unknownError, 67| 0| message: "Couldn't save object: \(object) key \"\(key)\" in Keychain") 68| 569| } 69| 569| } 70| |} <<<<<< EOF # path=./ParseSwiftTests.xctest.coverage.txt /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/ACLTests.swift: 1| |// 2| |// ACLTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 8/22/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |import XCTest 11| |@testable import ParseSwift 12| | 13| |class ACLTests: XCTestCase { 14| | 15| 7| override func setUp() { 16| 7| super.setUp() 17| 7| guard let url = URL(string: "http://localhost:1337/1") else { 18| 0| XCTFail("Should create valid URL") 19| 0| return 20| 7| } 21| 7| ParseSwift.initialize(applicationId: "applicationId", 22| 7| clientKey: "clientKey", 23| 7| masterKey: "masterKey", 24| 7| serverURL: url) 25| 7| } 26| | 27| 7| override func tearDown() { 28| 7| super.tearDown() 29| 7| try? ParseStorage.shared.secureStore.deleteAll() 30| 7| } 31| | 32| | struct User: ParseUser { 33| | //: Those are required for Object 34| | var objectId: String? 35| | var createdAt: Date? 36| | var updatedAt: Date? 37| | var ACL: ParseACL? 38| | 39| | // provided by User 40| | var username: String? 41| | var email: String? 42| | var password: String? 43| | 44| | // Your custom keys 45| | var customKey: String? 46| | } 47| | 48| | struct LoginSignupResponse: ParseUser { 49| | var objectId: String? 50| | var createdAt: Date? 51| | var sessionToken: String 52| | var updatedAt: Date? 53| | var ACL: ParseACL? 54| | 55| | // provided by User 56| | var username: String? 57| | var email: String? 58| | var password: String? 59| | 60| | // Your custom keys 61| | var customKey: String? 62| | 63| 2| init() { 64| 2| self.createdAt = Date() 65| 2| self.updatedAt = Date() 66| 2| self.objectId = "yarr" 67| 2| self.ACL = nil 68| 2| self.customKey = "blah" 69| 2| self.sessionToken = "myToken" 70| 2| self.username = "hello10" 71| 2| self.password = "world" 72| 2| self.email = "hello@parse.com" 73| 2| } 74| | } 75| | 76| 1| func testPublicAccess() { 77| 1| var acl = ParseACL() 78| 1| XCTAssertFalse(acl.publicRead) 79| 1| XCTAssertFalse(acl.publicWrite) 80| 1| 81| 1| acl.publicRead = true 82| 1| XCTAssertTrue(acl.publicRead) 83| 1| 84| 1| acl.publicWrite = true 85| 1| XCTAssertTrue(acl.publicWrite) 86| 1| } 87| | 88| 1| func testReadAccess() { 89| 1| var acl = ParseACL() 90| 1| XCTAssertFalse(acl.getReadAccess(userId: "someUserID")) 91| 1| XCTAssertFalse(acl.getReadAccess(roleName: "someRoleName")) 92| 1| 93| 1| acl.setReadAccess(userId: "someUserID", value: true) 94| 1| XCTAssertTrue(acl.getReadAccess(userId: "someUserID")) 95| 1| 96| 1| acl.setReadAccess(roleName: "someRoleName", value: true) 97| 1| XCTAssertTrue(acl.getReadAccess(roleName: "someRoleName")) 98| 1| 99| 1| acl.publicWrite = true 100| 1| XCTAssertTrue(acl.publicWrite) 101| 1| } 102| | 103| 1| func testWriteAccess() { 104| 1| var acl = ParseACL() 105| 1| XCTAssertFalse(acl.getWriteAccess(userId: "someUserID")) 106| 1| XCTAssertFalse(acl.getWriteAccess(roleName: "someRoleName")) 107| 1| 108| 1| acl.setWriteAccess(userId: "someUserID", value: true) 109| 1| XCTAssertTrue(acl.getWriteAccess(userId: "someUserID")) 110| 1| 111| 1| acl.setWriteAccess(roleName: "someRoleName", value: true) 112| 1| XCTAssertTrue(acl.getWriteAccess(roleName: "someRoleName")) 113| 1| 114| 1| acl.publicWrite = true 115| 1| XCTAssertTrue(acl.publicWrite) 116| 1| } 117| | 118| 1| func testCoding() { 119| 1| var acl = ParseACL() 120| 1| acl.setReadAccess(userId: "a", value: false) 121| 1| acl.setReadAccess(userId: "b", value: true) 122| 1| acl.setWriteAccess(userId: "c", value: false) 123| 1| acl.setWriteAccess(userId: "d", value: true) 124| 1| 125| 1| var encoded: Data? 126| 1| do { 127| 1| encoded = try JSONEncoder().encode(acl) 128| 1| } catch { 129| 0| XCTFail(error.localizedDescription) 130| 1| } 131| 1| 132| 1| if let dataToDecode = encoded { 133| 1| do { 134| 1| let decoded = try JSONDecoder().decode(ParseACL.self, from: dataToDecode) 135| 1| XCTAssertEqual(acl.getReadAccess(userId: "a"), decoded.getReadAccess(userId: "a")) 136| 1| XCTAssertEqual(acl.getReadAccess(userId: "b"), decoded.getReadAccess(userId: "b")) 137| 1| XCTAssertEqual(acl.getWriteAccess(userId: "c"), decoded.getWriteAccess(userId: "c")) 138| 1| XCTAssertEqual(acl.getWriteAccess(userId: "d"), decoded.getWriteAccess(userId: "d")) 139| 1| } catch { 140| 0| XCTFail(error.localizedDescription) 141| 1| } 142| 1| } else { 143| 0| XCTAssertNil(encoded) 144| 1| } 145| 1| 146| 1| } 147| | 148| 1| func testDefaultACLNoUser() { 149| 1| var newACL = ParseACL() 150| 1| let userId = "someUserID" 151| 1| newACL.setReadAccess(userId: userId, value: true) 152| 1| do { 153| 1| var defaultACL = try ParseACL.defaultACL() 154| 1| XCTAssertNotEqual(newACL, defaultACL) 155| 1| defaultACL = try ParseACL.setDefaultACL(defaultACL, withAccessForCurrentUser: true) 156| 1| if defaultACL.getReadAccess(userId: userId) { 157| 0| XCTFail("Shouldn't have set read access because there's no current user") 158| 1| } 159| 1| } catch { 160| 0| return 161| 1| } 162| 1| 163| 1| do { 164| 1| _ = try ParseACL.setDefaultACL(newACL, withAccessForCurrentUser: true) 165| 1| let defaultACL = try ParseACL.defaultACL() 166| 1| if !defaultACL.getReadAccess(userId: userId) { 167| 0| XCTFail("Should have set defaultACL with read access even though there's no current user") 168| 1| } 169| 1| } catch { 170| 0| return 171| 1| } 172| 1| } 173| | 174| 1| func testDefaultACL() { 175| 1| let loginResponse = LoginSignupResponse() 176| 1| 177| 3| MockURLProtocol.mockRequests { _ in 178| 3| do { 179| 3| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 180| 3| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 181| 3| } catch { 182| 0| return nil 183| 0| } 184| 0| } 185| 1| 186| 1| do { 187| 1| _ = try User.signup(username: "testUser", password: "password") 188| 1| } catch { 189| 0| XCTFail("Couldn't signUp user: \(error)") 190| 0| //return 191| 1| } 192| 1| 193| 1| guard let userObjectId = User.current?.objectId else { 194| 0| XCTFail("Couldn't get objectId of currentUser") 195| 0| return 196| 1| } 197| 1| 198| 1| var newACL = ParseACL() 199| 1| newACL.publicRead = true 200| 1| newACL.publicWrite = true 201| 1| do { 202| 1| var defaultACL = try ParseACL.defaultACL() 203| 1| XCTAssertNotEqual(newACL, defaultACL) 204| 1| _ = try ParseACL.setDefaultACL(newACL, withAccessForCurrentUser: true) 205| 1| defaultACL = try ParseACL.defaultACL() 206| 1| XCTAssertEqual(newACL.publicRead, defaultACL.publicRead) 207| 1| XCTAssertEqual(newACL.publicWrite, defaultACL.publicWrite) 208| 1| XCTAssertTrue(defaultACL.getReadAccess(userId: userObjectId)) 209| 1| XCTAssertTrue(defaultACL.getWriteAccess(userId: userObjectId)) 210| 1| 211| 1| } catch { 212| 0| XCTFail("Should have set new ACL. Error \(error)") 213| 1| } 214| 1| } 215| | 216| 1| func testDefaultACLDontUseCurrentUser() { 217| 1| let loginResponse = LoginSignupResponse() 218| 1| 219| 1| MockURLProtocol.mockRequests { _ in 220| 0| do { 221| 0| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 222| 0| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 223| 0| } catch { 224| 0| return nil 225| 0| } 226| 0| } 227| 1| do { 228| 1| _ = try User.signup(username: "testUser", password: "password") 229| 1| } catch { 230| 0| XCTFail("Couldn't signUp user: \(error.localizedDescription)") 231| 1| } 232| 1| 233| 1| guard let userObjectId = User.current?.objectId else { 234| 0| XCTFail("Couldn't get objectId of currentUser") 235| 0| return 236| 1| } 237| 1| 238| 1| var newACL = ParseACL() 239| 1| newACL.setReadAccess(userId: "someUserID", value: true) 240| 1| do { 241| 1| _ = try ParseACL.setDefaultACL(newACL, withAccessForCurrentUser: false) 242| 1| let defaultACL = try ParseACL.defaultACL() 243| 1| XCTAssertTrue(defaultACL.getReadAccess(userId: "someUserID")) 244| 1| XCTAssertFalse(defaultACL.getReadAccess(userId: userObjectId)) 245| 1| XCTAssertFalse(defaultACL.getWriteAccess(userId: userObjectId)) 246| 1| } catch { 247| 0| XCTFail("Should have set new ACL. Error \(error.localizedDescription)") 248| 1| } 249| 1| } 250| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/APICommandTests.swift: 1| |// 2| |// APICommandTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 7/19/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |import XCTest 11| |@testable import ParseSwift 12| | 13| |class APICommandTests: XCTestCase { 14| | 15| 5| override func setUp() { 16| 5| super.setUp() 17| 5| guard let url = URL(string: "http://localhost:1337/1") else { 18| 0| XCTFail("Should create valid URL") 19| 0| return 20| 5| } 21| 5| ParseSwift.initialize(applicationId: "applicationId", 22| 5| clientKey: "clientKey", 23| 5| masterKey: "masterKey", 24| 5| serverURL: url) 25| 5| } 26| | 27| 5| override func tearDown() { 28| 5| super.tearDown() 29| 5| MockURLProtocol.removeAll() 30| 5| try? ParseStorage.shared.secureStore.deleteAll() 31| 5| } 32| | 33| 1| func testExecuteCorrectly() { 34| 1| let originalObject = "test" 35| 1| MockURLProtocol.mockRequests { _ in 36| 1| do { 37| 1| return try MockURLResponse(string: originalObject, statusCode: 200, delay: 0.0) 38| 1| } catch { 39| 0| return nil 40| 0| } 41| 0| } 42| 1| do { 43| 1| let returnedObject = 44| 1| try API.Command(method: .GET, path: .login, params: nil, mapper: { (data) -> String in 45| 1| return try JSONDecoder().decode(String.self, from: data) 46| 1| }).execute(options: []) 47| 1| XCTAssertEqual(originalObject, returnedObject) 48| 1| 49| 1| } catch { 50| 0| XCTFail(error.localizedDescription) 51| 1| } 52| 1| } 53| | 54| | //This is how errors from the server should typically come in 55| 1| func testErrorFromParseServer() { 56| 1| let originalError = ParseError(code: .unknownError, message: "Couldn't decode") 57| 1| MockURLProtocol.mockRequests { _ in 58| 0| do { 59| 0| let encoded = try JSONEncoder().encode(originalError) 60| 0| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 61| 0| } catch { 62| 0| XCTFail("Should encode error") 63| 0| return nil 64| 0| } 65| 0| } 66| 1| 67| 1| do { 68| 1| _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (_) -> NoBody in 69| 1| throw originalError 70| 1| }).execute(options: []) 71| 1| XCTFail("Should have thrown an error") 72| 1| } catch { 73| 1| guard let error = error as? ParseError else { 74| 0| XCTFail("should be able unwrap final error to ParseError") 75| 0| return 76| 1| } 77| 1| XCTAssertEqual(originalError.code, error.code) 78| 1| } 79| 1| } 80| | 81| | //This is how errors HTTP errors should typically come in 82| 1| func testErrorHTTPJSON() { 83| 1| let errorKey = "error" 84| 1| let errorValue = "yarr" 85| 1| let codeKey = "code" 86| 1| let codeValue = 100500 87| 1| let responseDictionary: [String: Any] = [ 88| 1| errorKey: errorValue, 89| 1| codeKey: codeValue 90| 1| ] 91| 1| MockURLProtocol.mockRequests { _ in 92| 1| do { 93| 1| let json = try JSONSerialization.data(withJSONObject: responseDictionary, options: []) 94| 1| return MockURLResponse(data: json, statusCode: 400, delay: 0.0) 95| 1| } catch { 96| 0| XCTFail(error.localizedDescription) 97| 0| return nil 98| 0| } 99| 0| } 100| 1| do { 101| 1| _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (_) -> NoBody in 102| 1| throw ParseError(code: .connectionFailed, message: "Connection failed") 103| 1| }).execute(options: []) 104| 1| XCTFail("Should have thrown an error") 105| 1| } catch { 106| 1| guard let error = error as? ParseError else { 107| 0| XCTFail("should be able unwrap final error to ParseError") 108| 0| return 109| 1| } 110| 1| let unknownError = ParseError(code: .unknownError, message: "") 111| 1| XCTAssertEqual(unknownError.code, error.code) 112| 1| } 113| 1| } 114| | 115| | //This is less common as the HTTP won't be able to produce ParseErrors directly, but used for testing 116| 1| func testErrorHTTPReturnsParseError1() { 117| 1| let originalError = ParseError(code: .connectionFailed, message: "no connection") 118| 1| MockURLProtocol.mockRequests { response in 119| 1| let response = MockURLResponse(error: originalError) 120| 1| return response 121| 1| } 122| 1| do { 123| 1| _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (data) -> NoBody in 124| 0| return try JSONDecoder().decode(NoBody.self, from: data) 125| 0| }).execute(options: []) 126| 1| XCTFail("Should have thrown an error") 127| 1| } catch { 128| 1| guard let error = error as? ParseError else { 129| 0| XCTFail("should be able unwrap final error to ParseError") 130| 0| return 131| 1| } 132| 1| XCTAssertTrue(error.message.contains(originalError.message)) 133| 1| } 134| 1| } 135| | 136| | //This is less common as the HTTP won't be able to produce ParseErrors directly, but used for testing 137| 1| func testErrorHTTPReturnsParseError2() { 138| 1| let originalError = ParseError(code: .unknownError, message: "Couldn't decode") 139| 1| MockURLProtocol.mockRequests { _ in 140| 1| return MockURLResponse(error: originalError) 141| 1| } 142| 1| do { 143| 1| _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (_) -> NoBody in 144| 0| throw originalError 145| 0| }).execute(options: []) 146| 1| XCTFail("Should have thrown an error") 147| 1| } catch { 148| 1| guard let error = error as? ParseError else { 149| 0| XCTFail("should be able unwrap final error to ParseError") 150| 0| return 151| 1| } 152| 1| XCTAssertEqual(originalError.code, error.code) 153| 1| } 154| 1| } 155| | 156| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/AnyCodableTests/AnyCodableTests.swift: 1| |import XCTest 2| |@testable import ParseSwift 3| | 4| |class AnyCodableTests: XCTestCase { 5| 1| func testJSONDecoding() { 6| 1| guard let json = """ 7| 1| { 8| 1| "boolean": true, 9| 1| "integer": 1, 10| 1| "double": 3.14159265358979323846, 11| 1| "string": "string", 12| 1| "array": [1, 2, 3], 13| 1| "nested": { 14| 1| "a": "alpha", 15| 1| "b": "bravo", 16| 1| "c": "charlie" 17| 1| } 18| 1| } 19| 1| """.data(using: .utf8) else { 20| 0| XCTFail("Should unrap data as utf8") 21| 0| return 22| 1| } 23| 1| let decoder = JSONDecoder() 24| 1| do { 25| 1| let dictionary = try decoder.decode([String: AnyCodable].self, from: json) 26| 1| XCTAssertEqual(dictionary["boolean"]?.value as? Bool, true) 27| 1| XCTAssertEqual(dictionary["integer"]?.value as? Int, 1) 28| 1| guard let doubleValue = dictionary["double"]?.value as? Double else { 29| 0| XCTFail("Should unrap value as Double") 30| 0| return 31| 1| } 32| 1| XCTAssertEqual(doubleValue, 3.14159265358979323846, accuracy: 0.001) 33| 1| XCTAssertEqual(dictionary["string"]?.value as? String, "string") 34| 1| XCTAssertEqual(dictionary["array"]?.value as? [Int], [1, 2, 3]) 35| 1| XCTAssertEqual(dictionary["nested"]?.value as? [String: String], 36| 1| ["a": "alpha", "b": "bravo", "c": "charlie"]) 37| 1| } catch { 38| 0| XCTFail(error.localizedDescription) 39| 1| } 40| 1| } 41| 1| func testJSONEncoding() { 42| 1| let dictionary: [String: AnyCodable] = [ 43| 1| "boolean": true, 44| 1| "integer": 1, 45| 1| "double": 3.14159265358979323846, 46| 1| "string": "string", 47| 1| "array": [1, 2, 3], 48| 1| "nested": [ 49| 1| "a": "alpha", 50| 1| "b": "bravo", 51| 1| "c": "charlie" 52| 1| ] 53| 1| ] 54| 1| do { 55| 1| let encoder = JSONEncoder() 56| 1| let json = try encoder.encode(dictionary) 57| 1| guard let encodedJSONObject = 58| 1| try JSONSerialization.jsonObject(with: json, options: []) as? NSDictionary else { 59| 0| XCTFail("Should unrap JSON serialized object") 60| 0| return 61| 1| } 62| 1| guard let expected = """ 63| 1| { 64| 1| "boolean": true, 65| 1| "integer": 1, 66| 1| "double": 3.14159265358979323846, 67| 1| "string": "string", 68| 1| "array": [1, 2, 3], 69| 1| "nested": { 70| 1| "a": "alpha", 71| 1| "b": "bravo", 72| 1| "c": "charlie" 73| 1| } 74| 1| } 75| 1| """.data(using: .utf8) else { 76| 0| XCTFail("Should unrap data to utf8") 77| 0| return 78| 1| } 79| 1| guard let expectedJSONObject = 80| 1| try JSONSerialization.jsonObject(with: expected, options: []) as? NSDictionary else { 81| 0| XCTFail("Should unrap JSON serialized object") 82| 0| return 83| 1| } 84| 1| XCTAssertEqual(encodedJSONObject, expectedJSONObject) 85| 1| } catch { 86| 0| XCTFail(error.localizedDescription) 87| 1| } 88| 1| } 89| | static var allTests = [ 90| | ("testJSONDecoding", testJSONDecoding), 91| | ("testJSONEncoding", testJSONEncoding) 92| | ] 93| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/AnyCodableTests/AnyDecodableTests.swift: 1| |import XCTest 2| |@testable import ParseSwift 3| | 4| |class AnyDecodableTests: XCTestCase { 5| 1| func testJSONDecoding() { 6| 1| guard let json = """ 7| 1| { 8| 1| "boolean": true, 9| 1| "integer": 1, 10| 1| "double": 3.14159265358979323846, 11| 1| "string": "string", 12| 1| "array": [1, 2, 3], 13| 1| "nested": { 14| 1| "a": "alpha", 15| 1| "b": "bravo", 16| 1| "c": "charlie" 17| 1| } 18| 1| } 19| 1| """.data(using: .utf8) else { 20| 0| XCTFail("Should unrap data as utf8") 21| 0| return 22| 1| } 23| 1| do { 24| 1| let decoder = JSONDecoder() 25| 1| let dictionary = try decoder.decode([String: AnyDecodable].self, from: json) 26| 1| XCTAssertEqual(dictionary["boolean"]?.value as? Bool, true) 27| 1| XCTAssertEqual(dictionary["integer"]?.value as? Int, 1) 28| 1| guard let doubleValue = dictionary["double"]?.value as? Double else { 29| 0| XCTFail("Should unrap data as Double") 30| 0| return 31| 1| } 32| 1| XCTAssertEqual(doubleValue, 3.14159265358979323846, accuracy: 0.001) 33| 1| XCTAssertEqual(dictionary["string"]?.value as? String, "string") 34| 1| XCTAssertEqual(dictionary["array"]?.value as? [Int], [1, 2, 3]) 35| 1| XCTAssertEqual(dictionary["nested"]?.value as? [String: String], 36| 1| ["a": "alpha", "b": "bravo", "c": "charlie"]) 37| 1| } catch { 38| 0| XCTFail(error.localizedDescription) 39| 1| } 40| 1| } 41| | static var allTests = [ 42| | ("testJSONDecoding", testJSONDecoding) 43| | ] 44| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/AnyCodableTests/AnyEncodableTests.swift: 1| |import XCTest 2| |@testable import ParseSwift 3| | 4| |class AnyEncodableTests: XCTestCase { 5| 1| func testJSONEncoding() { 6| 1| let dictionary: [String: AnyEncodable] = [ 7| 1| "boolean": true, 8| 1| "integer": 1, 9| 1| "double": 3.14159265358979323846, 10| 1| "string": "string", 11| 1| "array": [1, 2, 3], 12| 1| "nested": [ 13| 1| "a": "alpha", 14| 1| "b": "bravo", 15| 1| "c": "charlie" 16| 1| ] 17| 1| ] 18| 1| do { 19| 1| let encoder = JSONEncoder() 20| 1| let json = try encoder.encode(dictionary) 21| 1| guard let encodedJSONObject = 22| 1| try JSONSerialization.jsonObject(with: json, options: []) as? NSDictionary else { 23| 0| XCTFail("Should encode JSON object") 24| 0| return 25| 1| } 26| 1| guard let expected = """ 27| 1| { 28| 1| "boolean": true, 29| 1| "integer": 1, 30| 1| "double": 3.14159265358979323846, 31| 1| "string": "string", 32| 1| "array": [1, 2, 3], 33| 1| "nested": { 34| 1| "a": "alpha", 35| 1| "b": "bravo", 36| 1| "c": "charlie" 37| 1| } 38| 1| } 39| 1| """.data(using: .utf8) else { 40| 0| XCTFail("Should unrap data to utf8") 41| 0| return 42| 1| } 43| 1| guard let expectedJSONObject = 44| 1| try JSONSerialization.jsonObject(with: expected, options: []) as? NSDictionary else { 45| 0| XCTFail("Should unrap serialized json object") 46| 0| return 47| 1| } 48| 1| XCTAssertEqual(encodedJSONObject, expectedJSONObject) 49| 1| } catch { 50| 0| XCTFail(error.localizedDescription) 51| 1| } 52| 1| } 53| | static var allTests = [ 54| | ("testJSONEncoding", testJSONEncoding) 55| | ] 56| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/KeychainStoreTests.swift: 1| |// 2| |// KeychainStoreTests.swift 3| |// ParseSwift 4| |// 5| |// Created by Florent Vilmart on 17-09-25. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |import XCTest 11| |@testable import ParseSwift 12| | 13| |class KeychainStoreTests: XCTestCase { 14| | var testStore: KeychainStore! 15| 15| override func setUp() { 16| 15| super.setUp() 17| 15| testStore = KeychainStore(service: "test") 18| 15| } 19| | 20| 15| override func tearDown() { 21| 15| super.tearDown() 22| 15| _ = testStore.removeAllObjects() 23| 15| } 24| | 25| 1| func testSetObject() { 26| 1| XCTAssertTrue(testStore.set(object: "yarr", forKey: "blah"), "Set should succeed") 27| 1| } 28| | 29| 1| func testGetObject() { 30| 1| let key = "yarrKey" 31| 1| let value = "yarrValue" 32| 1| testStore[key] = value 33| 1| guard let storedValue: String = testStore.object(forKey: key) else { 34| 0| XCTFail("Should unwrap to String") 35| 0| return 36| 1| } 37| 1| XCTAssertEqual(storedValue, value, "Values should be equal after get") 38| 1| } 39| | 40| 1| func testGetObjectSubscript() { 41| 1| let key = "yarrKey" 42| 1| let value = "yarrValue" 43| 1| testStore[key] = value 44| 1| guard let storedValue: String = testStore[key] else { 45| 0| XCTFail("Should unwrap to String") 46| 0| return 47| 1| } 48| 1| XCTAssertEqual(storedValue, value, "Values should be equal after get") 49| 1| } 50| | 51| 1| func testGetObjectStringTypedSubscript() { 52| 1| let key = "yarrKey" 53| 1| let value = "yarrValue" 54| 1| testStore[key] = value 55| 1| XCTAssertEqual(testStore[string: key], value, "Values should be equal after get") 56| 1| } 57| | 58| 1| func testGetObjectWrongStringTypedSubscript() { 59| 1| let key = "yarrKey" 60| 1| let value = 1 61| 1| testStore[key] = value 62| 1| XCTAssertNil(testStore[string: key], "Values should be nil after get") 63| 1| } 64| | 65| 1| func testGetObjectBoolTypedSubscript() { 66| 1| let key = "yarrKey" 67| 1| let value = true 68| 1| testStore[bool: key] = value 69| 1| XCTAssertEqual(testStore[bool: key], true, "Values should be equal after get") 70| 1| } 71| | 72| 1| func testGetObjectWrongBoolTypedSubscript() { 73| 1| let key = "yarrKey" 74| 1| let value = "Yo!" 75| 1| testStore[key] = value 76| 1| XCTAssertNil(testStore[bool: key], "Values should be nil after get") 77| 1| } 78| | 79| 1| func testGetAnyCodableObject() { 80| 1| let key = "yarrKey" 81| 1| let value: AnyCodable = "yarrValue" 82| 1| testStore[key] = value 83| 1| guard let storedValue: AnyCodable = testStore.object(forKey: key) else { 84| 0| XCTFail("Should unwrap to AnyCodable") 85| 0| return 86| 1| } 87| 1| XCTAssertEqual(storedValue, value, "Values should be equal after get") 88| 1| } 89| | 90| 1| func testSetComplextObject() { 91| 1| let complexObject: [AnyCodable] = [["key": "value"], "string2", 1234] 92| 1| testStore["complexObject"] = complexObject 93| 1| guard let retrievedObject: [AnyCodable] = testStore["complexObject"] else { 94| 0| return XCTFail("Should retrieve the object") 95| 1| } 96| 1| XCTAssertTrue(retrievedObject.count == 3) 97| 3| retrievedObject.enumerated().forEach { (offset, retrievedValue) in 98| 3| let value = complexObject[offset].value 99| 3| switch offset { 100| 3| case 0: 101| 1| guard let dict = value as? [String: String], 102| 1| let retrievedDictionary = retrievedValue.value as? [String: String] else { 103| 0| return XCTFail("Should be both dictionaries") 104| 1| } 105| 1| XCTAssertTrue(dict == retrievedDictionary) 106| 3| case 1: 107| 1| guard let string = value as? String, 108| 1| let retrievedString = retrievedValue.value as? String else { 109| 0| return XCTFail("Should be both strings") 110| 1| } 111| 1| XCTAssertTrue(string == retrievedString) 112| 3| case 2: 113| 1| guard let int = value as? Int, 114| 1| let retrievedInt = retrievedValue.value as? Int else { 115| 0| return XCTFail("Should be both ints") 116| 1| } 117| 1| XCTAssertTrue(int == retrievedInt) 118| 3| default: break 119| 3| } 120| 3| } 121| 1| } 122| | 123| 1| func testRemoveObject() { 124| 1| testStore["key1"] = "value1" 125| 1| XCTAssertNotNil(testStore[string: "key1"], "The value should be set") 126| 1| _ = testStore.removeObject(forKey: "key1") 127| 1| let key1Val: String? = testStore["key1"] 128| 1| XCTAssertNil(key1Val, "There should be no value after removal") 129| 1| } 130| | 131| 1| func testRemoveObjectSubscript() { 132| 1| testStore["key1"] = "value1" 133| 1| XCTAssertNotNil(testStore[string: "key1"], "The value should be set") 134| 1| testStore[string: "key1"] = nil 135| 1| let key1Val: String? = testStore["key1"] 136| 1| XCTAssertNil(key1Val, "There should be no value after removal") 137| 1| } 138| | 139| 1| func testRemoveAllObjects() { 140| 1| testStore["key1"] = "value1" 141| 1| testStore["key2"] = "value2" 142| 1| XCTAssertNotNil(testStore[string: "key1"], "The value should be set") 143| 1| XCTAssertNotNil(testStore[string: "key2"], "The value should be set") 144| 1| _ = testStore.removeAllObjects() 145| 1| let key1Val: String? = testStore["key1"] 146| 1| let key2Val: String? = testStore["key1"] 147| 1| XCTAssertNil(key1Val, "There should be no value after removal") 148| 1| XCTAssertNil(key2Val, "There should be no value after removal") 149| 1| } 150| | 151| 1| func testThreadSafeSet() { 152| 100| DispatchQueue.concurrentPerform(iterations: 100) { _ in 153| 100| XCTAssertTrue(testStore.set(object: "yarr", forKey: "pirate"), "Should set value") 154| 100| } 155| 1| } 156| | 157| 1| func testThreadSafeRemoveObject() { 158| 100| DispatchQueue.concurrentPerform(iterations: 100) { (index) in 159| 100| XCTAssertTrue(testStore.set(object: "yarr", forKey: "\(index)"), "Should set value") 160| 100| XCTAssertTrue(testStore.removeObject(forKey: "\(index)"), "Should set value") 161| 100| } 162| 1| } 163| | 164| 1| func testThreadSafeRemoveAllObjects() { 165| 100| DispatchQueue.concurrentPerform(iterations: 100) { (_) in 166| 100| XCTAssertTrue(testStore.set(object: "yarr", forKey: "pirate1"), "Should set value") 167| 100| XCTAssertTrue(testStore.set(object: "yarr", forKey: "pirate2"), "Should set value") 168| 100| XCTAssertTrue(testStore.removeAllObjects(), "Should set value") 169| 100| } 170| 1| } 171| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/NetworkMocking/MockURLProtocol.swift: 1| |// 2| |// MockURLProtocol.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey E. Baker on 7/19/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| | 11| |typealias MockURLProtocolRequestTestClosure = (URLRequest) -> Bool 12| |typealias MockURLResponseContructingClosure = (URLRequest) -> MockURLResponse? 13| | 14| |struct MockURLProtocolMock { 15| | var attempts: Int 16| | var test: MockURLProtocolRequestTestClosure 17| | var response: MockURLResponseContructingClosure 18| |} 19| | 20| |class MockURLProtocol: URLProtocol { 21| | var mock: MockURLProtocolMock? 22| | static var mocks: [MockURLProtocolMock] = [] 23| | private var loading: Bool = false 24| 0| var isLoading: Bool { 25| 0| return loading 26| 0| } 27| | 28| 73| class func mockRequests(response: @escaping MockURLResponseContructingClosure) { 29| 3.55k| mockRequestsPassing(NSIntegerMax, test: { _ in return true }, with: response) 30| 73| } 31| | 32| | class func mockRequestsPassing(_ test: @escaping MockURLProtocolRequestTestClosure, 33| 0| with response: @escaping MockURLResponseContructingClosure) { 34| 0| mockRequestsPassing(NSIntegerMax, test: test, with: response) 35| 0| } 36| | 37| | class func mockRequestsPassing(_ attempts: Int, test: @escaping MockURLProtocolRequestTestClosure, 38| 73| with response: @escaping MockURLResponseContructingClosure) { 39| 73| let mock = MockURLProtocolMock(attempts: attempts, test: test, response: response) 40| 73| mocks.append(mock) 41| 73| if mocks.count == 1 { 42| 71| URLProtocol.registerClass(MockURLProtocol.self) 43| 73| } 44| 73| } 45| | 46| 84| class func removeAll() { 47| 84| if !mocks.isEmpty { 48| 71| URLProtocol.unregisterClass(MockURLProtocol.self) 49| 84| } 50| 84| mocks.removeAll() 51| 84| } 52| | 53| 3.55k| class func firstMockForRequest(_ request: URLRequest) -> MockURLProtocolMock? { 54| 3.55k| for mock in mocks { 55| 3.55k| if (mock.attempts > 0) && mock.test(request) { 56| 3.55k| return mock 57| 3.55k| } 58| 0| } 59| 0| return nil 60| 3.55k| } 61| | 62| 0| override class func canInit(with request: URLRequest) -> Bool { 63| 0| return MockURLProtocol.firstMockForRequest(request) != nil 64| 0| } 65| | 66| 1.77k| override class func canInit(with task: URLSessionTask) -> Bool { 67| 1.77k| guard let originalRequest = task.originalRequest else { 68| 0| return false 69| 1.77k| } 70| 1.77k| return MockURLProtocol.firstMockForRequest(originalRequest) != nil 71| 1.77k| } 72| | 73| 1.77k| override class func canonicalRequest(for request: URLRequest) -> URLRequest { 74| 1.77k| return request 75| 1.77k| } 76| | 77| 1.77k| override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { 78| 1.77k| self.mock = nil 79| 1.77k| super.init(request: request, cachedResponse: cachedResponse, client: client) 80| 1.77k| guard let mock = MockURLProtocol.firstMockForRequest(request) else { 81| 0| self.mock = nil 82| 0| return 83| 1.77k| } 84| 1.77k| self.mock = mock 85| 1.77k| } 86| | 87| 1.77k| override func startLoading() { 88| 1.77k| self.loading = true 89| 1.77k| self.mock?.attempts -= 1 90| 1.77k| guard let response = self.mock?.response(request) else { 91| 0| return 92| 1.77k| } 93| 1.77k| 94| 1.77k| if let error = response.error { 95| 2| DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + response.delay * Double(NSEC_PER_SEC)) { 96| 2| 97| 2| if self.loading { 98| 2| self.client?.urlProtocol(self, didFailWithError: error) 99| 2| } 100| 2| 101| 2| } 102| 2| return 103| 1.77k| } 104| 1.77k| 105| 1.77k| guard let url = request.url, 106| 1.77k| let urlResponse = HTTPURLResponse(url: url, statusCode: response.statusCode, 107| 1.77k| httpVersion: "HTTP/2", headerFields: response.headerFields) else { 108| 0| return 109| 1.77k| } 110| 1.77k| 111| 1.77k| DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + response.delay * Double(NSEC_PER_SEC)) { 112| 1.77k| 113| 1.77k| if !self.loading { 114| 0| return 115| 1.77k| } 116| 1.77k| 117| 1.77k| self.client?.urlProtocol(self, didReceive: urlResponse, cacheStoragePolicy: .notAllowed) 118| 1.77k| if let data = response.responseData { 119| 1.77k| self.client?.urlProtocol(self, didLoad: data) 120| 1.77k| } 121| 1.77k| self.client?.urlProtocolDidFinishLoading(self) 122| 1.77k| } 123| 1.77k| 124| 1.77k| } 125| | 126| 1.77k| override func stopLoading() { 127| 1.77k| self.loading = false 128| 1.77k| } 129| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/NetworkMocking/MockURLResponse.swift: 1| |// 2| |// MockURLResponse.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 7/18/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |@testable import ParseSwift 11| | 12| |struct MockURLResponse { 13| | var statusCode: Int = 200 14| 1.77k| var headerFields = [String: String]() 15| | var responseData: Data? 16| | var delay: TimeInterval! 17| | var error: Error? 18| | 19| 2| init(error: Error) { 20| 2| self.delay = .init(0.0) 21| 2| self.error = error 22| 2| self.responseData = nil 23| 2| self.statusCode = 400 24| 2| } 25| | 26| 0| init(string: String) throws { 27| 0| try self.init(string: string, statusCode: 200, delay: .init(0.0)) 28| 0| } 29| | 30| | init(string: String, statusCode: Int, delay: TimeInterval, 31| 1| headerFields: [String: String] = ["Content-Type": "application/json"]) throws { 32| 1| 33| 1| do { 34| 1| let encoded = try JSONEncoder().encode(string) 35| 1| self.init(data: encoded, statusCode: statusCode, delay: delay, headerFields: headerFields) 36| 1| } catch { 37| 0| throw ParseError(code: .unknownError, message: "unable to convert string to data") 38| 1| } 39| 1| } 40| | 41| | init(data: Data, statusCode: Int, delay: TimeInterval, 42| 1.77k| headerFields: [String: String] = ["Content-Type": "application/json"]) { 43| 1.77k| self.statusCode = statusCode 44| 1.77k| self.headerFields = headerFields 45| 1.77k| self.responseData = data 46| 1.77k| self.delay = delay 47| 1.77k| self.error = nil 48| 1.77k| } 49| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/ParseEncoderTests.swift: 1| |// 2| |// ParseEncoderTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Pranjal Satija on 8/7/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import XCTest 10| |@testable import ParseSwift 11| | 12| |class ParseEncoderTests: XCTestCase { 13| | struct Address: Codable { 14| | let street: String 15| | let city: String 16| | } 17| | 18| | struct Name: Codable { 19| | let first: String 20| | let last: String 21| | } 22| | 23| | struct Person: Codable { 24| | let addresses: [String: Address] 25| | let age: Int 26| | let name: Name 27| | let nicknames: [Name] 28| | let phoneNumbers: [String] 29| | } 30| | 31| 2| func parseEncoding(for object: T) -> Data { 32| 2| let encoder = ParseEncoder() 33| 2| encoder.jsonEncoder.outputFormatting = .sortedKeys 34| 2| 35| 2| guard let encoding = try? encoder.encode(object) else { 36| 0| XCTFail("Couldn't get a Parse encoding.") 37| 0| return Data() 38| 2| } 39| 2| 40| 2| return encoding 41| 2| } 42| | 43| 2| func referenceEncoding(for object: T) -> Data { 44| 2| let encoder = JSONEncoder() 45| 2| encoder.outputFormatting = .sortedKeys 46| 2| 47| 2| guard let encoding = try? encoder.encode(object) else { 48| 0| XCTFail("Couldn't get a reference encoding.") 49| 0| return Data() 50| 2| } 51| 2| 52| 2| return encoding 53| 2| } 54| | 55| 1| func test_encodingScalarValue() { 56| 1| let encoded = parseEncoding(for: 5) 57| 1| let reference = referenceEncoding(for: ["": 5]) 58| 1| XCTAssertEqual(encoded, reference) 59| 1| } 60| | 61| 1| func test_encodingComplexValue() { 62| 1| let value = Person( 63| 1| addresses: [ 64| 1| "home": Address(street: "Parse St.", city: "San Francisco"), 65| 1| "work": Address(street: "Server Ave.", city: "Seattle") 66| 1| ], 67| 1| age: 21, 68| 1| name: Name(first: "Parse", last: "User"), 69| 1| nicknames: [ 70| 1| Name(first: "Swift", last: "Developer"), 71| 1| Name(first: "iOS", last: "Engineer") 72| 1| ], 73| 1| phoneNumbers: [ 74| 1| "1-800-PARSE", 75| 1| "1-999-SWIFT" 76| 1| ] 77| 1| ) 78| 1| 79| 1| let encoded = parseEncoding(for: value) 80| 1| let reference = referenceEncoding(for: value) 81| 1| XCTAssertEqual(encoded, reference) 82| 1| } 83| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/ParseInstallationTests.swift: 1| |// 2| |// ParseInstallationTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 9/7/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |#if canImport(UIKit) 11| |import UIKit 12| |#elseif canImport(AppKit) 13| |import AppKit 14| |#endif 15| |import XCTest 16| |@testable import ParseSwift 17| | 18| |class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_length 19| | 20| | struct User: ParseUser { 21| | //: Those are required for Object 22| | var objectId: String? 23| | var createdAt: Date? 24| | var updatedAt: Date? 25| | var ACL: ParseACL? 26| | 27| | // provided by User 28| | var username: String? 29| | var email: String? 30| | var password: String? 31| | 32| | // Your custom keys 33| | var customKey: String? 34| | } 35| | 36| | struct LoginSignupResponse: ParseUser { 37| | var objectId: String? 38| | var createdAt: Date? 39| | var sessionToken: String 40| | var updatedAt: Date? 41| | var ACL: ParseACL? 42| | 43| | // provided by User 44| | var username: String? 45| | var email: String? 46| | var password: String? 47| | 48| | // Your custom keys 49| | var customKey: String? 50| | 51| 11| init() { 52| 11| self.createdAt = Date() 53| 11| self.updatedAt = Date() 54| 11| self.objectId = "yarr" 55| 11| self.ACL = nil 56| 11| self.customKey = "blah" 57| 11| self.sessionToken = "myToken" 58| 11| self.username = "hello10" 59| 11| self.password = "world" 60| 11| self.email = "hello@parse.com" 61| 11| } 62| | } 63| | 64| | struct Installation: ParseInstallation { 65| | var installationId: String? 66| | var deviceType: String? 67| | var deviceToken: String? 68| | var badge: Int? 69| | var timeZone: String? 70| | var channels: [String]? 71| | var appName: String? 72| | var appIdentifier: String? 73| | var appVersion: String? 74| | var parseVersion: String? 75| | var localeIdentifier: String? 76| | var objectId: String? 77| | var createdAt: Date? 78| | var updatedAt: Date? 79| | var ACL: ParseACL? 80| | var customKey: String? 81| | } 82| | 83| | let testInstallationObjectId = "yarr" 84| | 85| 11| override func setUp() { 86| 11| super.setUp() 87| 11| guard let url = URL(string: "http://localhost:1337/1") else { 88| 0| XCTFail("Should create valid URL") 89| 0| return 90| 11| } 91| 11| ParseSwift.initialize(applicationId: "applicationId", 92| 11| clientKey: "clientKey", 93| 11| masterKey: "masterKey", 94| 11| serverURL: url) 95| 11| userLogin() 96| 11| } 97| | 98| 11| override func tearDown() { 99| 11| super.tearDown() 100| 11| MockURLProtocol.removeAll() 101| 11| try? ParseStorage.shared.secureStore.deleteAll() 102| 11| } 103| | 104| 11| func userLogin() { 105| 11| let loginResponse = LoginSignupResponse() 106| 11| 107| 11| MockURLProtocol.mockRequests { _ in 108| 11| do { 109| 11| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 110| 11| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 111| 11| } catch { 112| 0| return nil 113| 0| } 114| 0| } 115| 11| do { 116| 11| _ = try User.login(username: loginResponse.username!, password: loginResponse.password!) 117| 11| MockURLProtocol.removeAll() 118| 11| } catch { 119| 0| XCTFail("Should login") 120| 11| } 121| 11| } 122| | 123| 1| func testNewInstallationIdentifierIsLowercase() { 124| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 125| 1| DispatchQueue.main.async { 126| 1| guard let installationIdFromContainer 127| 1| = Installation.currentInstallationContainer.installationId else { 128| 0| XCTFail("Should have retreived installationId from container") 129| 0| expectation1.fulfill() 130| 0| return 131| 1| } 132| 1| 133| 1| XCTAssertEqual(installationIdFromContainer, installationIdFromContainer.lowercased()) 134| 1| 135| 1| guard let installationIdFromCurrent = Installation.current?.installationId else { 136| 0| XCTFail("Should have retreived installationId from container") 137| 0| expectation1.fulfill() 138| 0| return 139| 1| } 140| 1| 141| 1| XCTAssertEqual(installationIdFromCurrent, installationIdFromCurrent.lowercased()) 142| 1| XCTAssertEqual(installationIdFromContainer, installationIdFromCurrent) 143| 1| expectation1.fulfill() 144| 1| } 145| 1| wait(for: [expectation1], timeout: 10.0) 146| 1| } 147| | 148| 1| func testInstallationMutableValuesCanBeChangedInMemory() { 149| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 150| 1| DispatchQueue.main.async { 151| 1| guard let originalInstallation = Installation.current else { 152| 0| XCTFail("All of these Installation values should have unwraped") 153| 0| expectation1.fulfill() 154| 0| return 155| 1| } 156| 1| 157| 1| Installation.current?.customKey = "Changed" 158| 1| XCTAssertNotEqual(originalInstallation.customKey, Installation.current?.customKey) 159| 1| expectation1.fulfill() 160| 1| } 161| 1| wait(for: [expectation1], timeout: 10.0) 162| 1| } 163| | 164| 1| func testInstallationCustomValuesNotSavedToKeychain() { 165| 1| Installation.current?.customKey = "Changed" 166| 1| Installation.saveCurrentContainer() 167| 1| guard let keychainInstallation: CurrentInstallationContainer 168| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { 169| 0| return 170| 1| } 171| 1| XCTAssertNil(keychainInstallation.currentInstallation?.customKey) 172| 1| } 173| | 174| 1| func testInstallationImmutableFieldsCannotBeChangedInMemory() { 175| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 176| 1| DispatchQueue.main.async { 177| 1| guard let originalInstallation = Installation.current, 178| 1| let originalInstallationId = originalInstallation.installationId, 179| 1| let originalDeviceType = originalInstallation.deviceType, 180| 1| let originalBadge = originalInstallation.badge, 181| 1| let originalTimeZone = originalInstallation.timeZone, 182| 1| let originalAppName = originalInstallation.appName, 183| 1| let originalAppIdentifier = originalInstallation.appIdentifier, 184| 1| let originalAppVersion = originalInstallation.appVersion, 185| 1| let originalParseVersion = originalInstallation.parseVersion, 186| 1| let originalLocaleIdentifier = originalInstallation.localeIdentifier 187| 1| else { 188| 0| XCTFail("All of these Installation values should have unwraped") 189| 0| expectation1.fulfill() 190| 0| return 191| 1| } 192| 1| 193| 1| Installation.current?.installationId = "changed" 194| 1| Installation.current?.deviceType = "changed" 195| 1| Installation.current?.badge = 500 196| 1| Installation.current?.timeZone = "changed" 197| 1| Installation.current?.appName = "changed" 198| 1| Installation.current?.appIdentifier = "changed" 199| 1| Installation.current?.appVersion = "changed" 200| 1| Installation.current?.parseVersion = "changed" 201| 1| Installation.current?.localeIdentifier = "changed" 202| 1| 203| 1| XCTAssertEqual(originalInstallationId, Installation.current?.installationId) 204| 1| XCTAssertEqual(originalDeviceType, Installation.current?.deviceType) 205| 1| XCTAssertEqual(originalBadge, Installation.current?.badge) 206| 1| XCTAssertEqual(originalTimeZone, Installation.current?.timeZone) 207| 1| XCTAssertEqual(originalAppName, Installation.current?.appName) 208| 1| XCTAssertEqual(originalAppIdentifier, Installation.current?.appIdentifier) 209| 1| XCTAssertEqual(originalAppVersion, Installation.current?.appVersion) 210| 1| XCTAssertEqual(originalParseVersion, Installation.current?.parseVersion) 211| 1| XCTAssertEqual(originalLocaleIdentifier, Installation.current?.localeIdentifier) 212| 1| expectation1.fulfill() 213| 1| } 214| 1| wait(for: [expectation1], timeout: 10.0) 215| 1| } 216| | 217| | // swiftlint:disable:next function_body_length 218| 1| func testInstallationImmutableFieldsCannotBeChangedInKeychain() { 219| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 220| 1| DispatchQueue.main.async { 221| 1| guard let originalInstallation = Installation.current, 222| 1| let originalInstallationId = originalInstallation.installationId, 223| 1| let originalDeviceType = originalInstallation.deviceType, 224| 1| let originalBadge = originalInstallation.badge, 225| 1| let originalTimeZone = originalInstallation.timeZone, 226| 1| let originalAppName = originalInstallation.appName, 227| 1| let originalAppIdentifier = originalInstallation.appIdentifier, 228| 1| let originalAppVersion = originalInstallation.appVersion, 229| 1| let originalParseVersion = originalInstallation.parseVersion, 230| 1| let originalLocaleIdentifier = originalInstallation.localeIdentifier 231| 1| else { 232| 0| XCTFail("All of these Installation values should have unwraped") 233| 0| expectation1.fulfill() 234| 0| return 235| 1| } 236| 1| 237| 1| Installation.current?.installationId = "changed" 238| 1| Installation.current?.deviceType = "changed" 239| 1| Installation.current?.badge = 500 240| 1| Installation.current?.timeZone = "changed" 241| 1| Installation.current?.appName = "changed" 242| 1| Installation.current?.appIdentifier = "changed" 243| 1| Installation.current?.appVersion = "changed" 244| 1| Installation.current?.parseVersion = "changed" 245| 1| Installation.current?.localeIdentifier = "changed" 246| 1| 247| 1| Installation.saveCurrentContainer() 248| 1| 249| 1| guard let keychainInstallation: CurrentInstallationContainer 250| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) else { 251| 0| expectation1.fulfill() 252| 0| return 253| 1| } 254| 1| XCTAssertEqual(originalInstallationId, keychainInstallation.currentInstallation?.installationId) 255| 1| XCTAssertEqual(originalDeviceType, keychainInstallation.currentInstallation?.deviceType) 256| 1| XCTAssertEqual(originalBadge, keychainInstallation.currentInstallation?.badge) 257| 1| XCTAssertEqual(originalTimeZone, keychainInstallation.currentInstallation?.timeZone) 258| 1| XCTAssertEqual(originalAppName, keychainInstallation.currentInstallation?.appName) 259| 1| XCTAssertEqual(originalAppIdentifier, keychainInstallation.currentInstallation?.appIdentifier) 260| 1| XCTAssertEqual(originalAppVersion, keychainInstallation.currentInstallation?.appVersion) 261| 1| XCTAssertEqual(originalParseVersion, keychainInstallation.currentInstallation?.parseVersion) 262| 1| XCTAssertEqual(originalLocaleIdentifier, keychainInstallation.currentInstallation?.localeIdentifier) 263| 1| expectation1.fulfill() 264| 1| } 265| 1| wait(for: [expectation1], timeout: 10.0) 266| 1| } 267| | 268| 1| func testInstallationHasApplicationBadge() { 269| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 270| 1| DispatchQueue.main.async { 271| 1| #if canImport(UIKit) && !os(watchOS) 272| 1| UIApplication.shared.applicationIconBadgeNumber = 10 273| 1| guard let installationBadge = Installation.current?.badge else { 274| 0| XCTFail("Should have retreived badge") 275| 0| expectation1.fulfill() 276| 0| return 277| 1| } 278| 1| XCTAssertEqual(installationBadge, 10) 279| 1| #elseif canImport(AppKit) 280| 1| NSApplication.shared.dockTile.badgeLabel = "10" 281| 1| guard let installationBadge = Installation.current?.badge else { 282| 1| XCTFail("Should have retreived badge") 283| 1| expectation1.fulfill() 284| 1| return 285| 1| } 286| 1| XCTAssertEqual(installationBadge, 10) 287| 1| #endif 288| 1| expectation1.fulfill() 289| 1| } 290| 1| wait(for: [expectation1], timeout: 10.0) 291| 1| } 292| | 293| 5| func testUpdate() { 294| 5| var installation = Installation() 295| 5| installation.objectId = testInstallationObjectId 296| 5| installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 297| 5| installation.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 298| 5| installation.ACL = nil 299| 5| 300| 5| var installationOnServer = installation 301| 5| installationOnServer.updatedAt = Date() 302| 5| 303| 5| let encoded: Data! 304| 5| do { 305| 5| encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) 306| 5| //Get dates in correct format from ParseDecoding strategy 307| 5| installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) 308| 5| } catch { 309| 0| XCTFail("Should encode/decode. Error \(error)") 310| 0| return 311| 5| } 312| 5| MockURLProtocol.mockRequests { _ in 313| 5| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 314| 5| } 315| 5| let expectation1 = XCTestExpectation(description: "Update installation1") 316| 5| DispatchQueue.main.async { 317| 5| do { 318| 5| let saved = try installation.save() 319| 5| guard let savedCreatedAt = saved.createdAt, 320| 5| let savedUpdatedAt = saved.updatedAt else { 321| 0| XCTFail("Should unwrap dates") 322| 0| expectation1.fulfill() 323| 0| return 324| 5| } 325| 5| guard let originalCreatedAt = installation.createdAt, 326| 5| let originalUpdatedAt = installation.updatedAt else { 327| 0| XCTFail("Should unwrap dates") 328| 0| expectation1.fulfill() 329| 0| return 330| 5| } 331| 5| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 332| 5| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 333| 5| XCTAssertNil(saved.ACL) 334| 5| } catch { 335| 0| XCTFail(error.localizedDescription) 336| 5| } 337| 5| expectation1.fulfill() 338| 5| } 339| 5| wait(for: [expectation1], timeout: 10.0) 340| 5| } 341| | 342| 1| func testUpdateToCurrentInstallation() { 343| 1| testUpdate() 344| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 345| 1| DispatchQueue.main.async { 346| 1| guard let savedObjectId = Installation.current?.objectId else { 347| 0| XCTFail("Should unwrap dates") 348| 0| expectation1.fulfill() 349| 0| return 350| 1| } 351| 1| XCTAssertEqual(savedObjectId, self.testInstallationObjectId) 352| 1| expectation1.fulfill() 353| 1| } 354| 1| wait(for: [expectation1], timeout: 10.0) 355| 1| } 356| | 357| | // swiftlint:disable:next function_body_length 358| 1| func updateAsync(installation: Installation, installationOnServer: Installation, callbackQueue: DispatchQueue) { 359| 1| 360| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 361| 1| installation.save(options: [], callbackQueue: callbackQueue) { result in 362| 1| 363| 1| switch result { 364| 1| 365| 1| case .success(let saved): 366| 1| guard let savedCreatedAt = saved.createdAt, 367| 1| let savedUpdatedAt = saved.updatedAt else { 368| 0| XCTFail("Should unwrap dates") 369| 0| expectation1.fulfill() 370| 0| return 371| 1| } 372| 1| guard let originalCreatedAt = installation.createdAt, 373| 1| let originalUpdatedAt = installation.updatedAt else { 374| 0| XCTFail("Should unwrap dates") 375| 0| expectation1.fulfill() 376| 0| return 377| 1| } 378| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 379| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 380| 1| XCTAssertNil(saved.ACL) 381| 1| 382| 1| if callbackQueue != .main { 383| 0| DispatchQueue.main.async { 384| 0| guard let savedCreatedAt = Installation.current?.createdAt, 385| 0| let savedUpdatedAt = Installation.current?.updatedAt else { 386| 0| XCTFail("Should unwrap dates") 387| 0| expectation1.fulfill() 388| 0| return 389| 0| } 390| 0| guard let originalCreatedAt = installation.createdAt, 391| 0| let originalUpdatedAt = installation.updatedAt else { 392| 0| XCTFail("Should unwrap dates") 393| 0| expectation1.fulfill() 394| 0| return 395| 0| } 396| 0| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 397| 0| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 398| 0| XCTAssertNil(Installation.current?.ACL) 399| 0| expectation1.fulfill() 400| 0| } 401| 1| } else { 402| 1| guard let savedCreatedAt = Installation.current?.createdAt, 403| 1| let savedUpdatedAt = Installation.current?.updatedAt else { 404| 0| XCTFail("Should unwrap dates") 405| 0| expectation1.fulfill() 406| 0| return 407| 1| } 408| 1| guard let originalCreatedAt = installation.createdAt, 409| 1| let originalUpdatedAt = installation.updatedAt else { 410| 0| XCTFail("Should unwrap dates") 411| 0| expectation1.fulfill() 412| 0| return 413| 1| } 414| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 415| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 416| 1| XCTAssertNil(Installation.current?.ACL) 417| 1| expectation1.fulfill() 418| 1| } 419| 1| 420| 1| case .failure(let error): 421| 0| XCTFail(error.localizedDescription) 422| 0| expectation1.fulfill() 423| 1| } 424| 1| } 425| 1| wait(for: [expectation1], timeout: 10.0) 426| 1| } 427| | 428| 1| func testUpdateAsyncMainQueue() { 429| 1| testUpdate() 430| 1| MockURLProtocol.removeAll() 431| 1| 432| 1| var installation = Installation() 433| 1| installation.objectId = testInstallationObjectId 434| 1| installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 435| 1| installation.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 436| 1| installation.ACL = nil 437| 1| 438| 1| var installationOnServer = installation 439| 1| installationOnServer.updatedAt = Date() 440| 1| let encoded: Data! 441| 1| do { 442| 1| let encodedOriginal = try installation.getEncoder(skipKeys: false).encode(installation) 443| 1| //Get dates in correct format from ParseDecoding strategy 444| 1| installation = try installation.getDecoder().decode(Installation.self, from: encodedOriginal) 445| 1| 446| 1| encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) 447| 1| //Get dates in correct format from ParseDecoding strategy 448| 1| installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) 449| 1| } catch { 450| 0| XCTFail("Should encode/decode. Error \(error)") 451| 0| return 452| 1| } 453| 1| MockURLProtocol.mockRequests { _ in 454| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 455| 1| } 456| 1| 457| 1| self.updateAsync(installation: installation, installationOnServer: installationOnServer, callbackQueue: .main) 458| 1| } 459| | 460| 1| func testFetchUpdatedCurrentInstallation() { // swiftlint:disable:this function_body_length 461| 1| testUpdate() 462| 1| MockURLProtocol.removeAll() 463| 1| 464| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 465| 1| DispatchQueue.main.async { 466| 1| guard let installation = Installation.current, 467| 1| let savedObjectId = installation.objectId else { 468| 0| XCTFail("Should unwrap") 469| 0| expectation1.fulfill() 470| 0| return 471| 1| } 472| 1| XCTAssertEqual(savedObjectId, self.testInstallationObjectId) 473| 1| 474| 1| var installationOnServer = installation 475| 1| installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) 476| 1| 477| 1| let encoded: Data! 478| 1| do { 479| 1| encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) 480| 1| //Get dates in correct format from ParseDecoding strategy 481| 1| installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) 482| 1| } catch { 483| 0| XCTFail("Should encode/decode. Error \(error)") 484| 0| expectation1.fulfill() 485| 0| return 486| 1| } 487| 1| MockURLProtocol.mockRequests { _ in 488| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 489| 1| } 490| 1| 491| 1| do { 492| 1| let fetched = try installation.fetch(options: [.useMasterKey]) 493| 1| XCTAssert(fetched.hasSameObjectId(as: installationOnServer)) 494| 1| guard let fetchedCreatedAt = fetched.createdAt, 495| 1| let fetchedUpdatedAt = fetched.updatedAt else { 496| 0| XCTFail("Should unwrap dates") 497| 0| expectation1.fulfill() 498| 0| return 499| 1| } 500| 1| guard let originalCreatedAt = installation.createdAt, 501| 1| let originalUpdatedAt = installation.updatedAt, 502| 1| let serverUpdatedAt = installationOnServer.updatedAt else { 503| 0| XCTFail("Should unwrap dates") 504| 0| expectation1.fulfill() 505| 0| return 506| 1| } 507| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 508| 1| XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt) 509| 1| XCTAssertEqual(fetchedUpdatedAt, serverUpdatedAt) 510| 1| 511| 1| //Should be updated in memory 512| 1| guard let updatedCurrentDate = Installation.current?.updatedAt else { 513| 0| XCTFail("Should unwrap current date") 514| 0| expectation1.fulfill() 515| 0| return 516| 1| } 517| 1| XCTAssertEqual(updatedCurrentDate, serverUpdatedAt) 518| 1| 519| 1| //Shold be updated in Keychain 520| 1| guard let keychainInstallation: CurrentInstallationContainer 521| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation), 522| 1| let keychainUpdatedCurrentDate = keychainInstallation.currentInstallation?.updatedAt else { 523| 0| XCTFail("Should get object from Keychain") 524| 0| expectation1.fulfill() 525| 0| return 526| 1| } 527| 1| XCTAssertEqual(keychainUpdatedCurrentDate, serverUpdatedAt) 528| 1| 529| 1| } catch { 530| 0| XCTFail(error.localizedDescription) 531| 1| } 532| 1| 533| 1| expectation1.fulfill() 534| 1| } 535| 1| wait(for: [expectation1], timeout: 10.0) 536| 1| } 537| | 538| 1| func testFetchUpdatedCurrentInstallationAsync() { // swiftlint:disable:this function_body_length 539| 1| testUpdate() 540| 1| MockURLProtocol.removeAll() 541| 1| 542| 1| let expectation1 = XCTestExpectation(description: "Update installation1") 543| 1| DispatchQueue.main.async { 544| 1| guard let installation = Installation.current else { 545| 0| XCTFail("Should unwrap") 546| 0| expectation1.fulfill() 547| 0| return 548| 1| } 549| 1| 550| 1| var installationOnServer = installation 551| 1| installationOnServer.updatedAt = installation.updatedAt?.addingTimeInterval(+300) 552| 1| 553| 1| let encoded: Data! 554| 1| do { 555| 1| encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) 556| 1| //Get dates in correct format from ParseDecoding strategy 557| 1| installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) 558| 1| } catch { 559| 0| XCTFail("Should encode/decode. Error \(error)") 560| 0| expectation1.fulfill() 561| 0| return 562| 1| } 563| 1| MockURLProtocol.mockRequests { _ in 564| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 565| 1| } 566| 1| 567| 1| installation.fetch(options: [], callbackQueue: .main) { result in 568| 1| 569| 1| switch result { 570| 1| case .success(let fetched): 571| 1| XCTAssert(fetched.hasSameObjectId(as: installationOnServer)) 572| 1| guard let fetchedCreatedAt = fetched.createdAt, 573| 1| let fetchedUpdatedAt = fetched.updatedAt else { 574| 0| XCTFail("Should unwrap dates") 575| 0| expectation1.fulfill() 576| 0| return 577| 1| } 578| 1| guard let originalCreatedAt = installation.createdAt, 579| 1| let originalUpdatedAt = installation.updatedAt, 580| 1| let serverUpdatedAt = installationOnServer.updatedAt else { 581| 0| XCTFail("Should unwrap dates") 582| 0| expectation1.fulfill() 583| 0| return 584| 1| } 585| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 586| 1| XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt) 587| 1| XCTAssertEqual(fetchedUpdatedAt, serverUpdatedAt) 588| 1| 589| 1| //Should be updated in memory 590| 1| guard let updatedCurrentDate = Installation.current?.updatedAt else { 591| 0| XCTFail("Should unwrap current date") 592| 0| expectation1.fulfill() 593| 0| return 594| 1| } 595| 1| XCTAssertEqual(updatedCurrentDate, serverUpdatedAt) 596| 1| 597| 1| //Shold be updated in Keychain 598| 1| guard let keychainInstallation: CurrentInstallationContainer 599| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation), 600| 1| let keychainUpdatedCurrentDate = keychainInstallation.currentInstallation?.updatedAt else { 601| 0| XCTFail("Should get object from Keychain") 602| 0| expectation1.fulfill() 603| 0| return 604| 1| } 605| 1| XCTAssertEqual(keychainUpdatedCurrentDate, serverUpdatedAt) 606| 1| case .failure(let error): 607| 0| XCTFail(error.localizedDescription) 608| 1| } 609| 1| expectation1.fulfill() 610| 1| } 611| 1| } 612| 1| wait(for: [expectation1], timeout: 10.0) 613| 1| } 614| |} // swiftlint:disable:this file_length /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/ParseObjectBatchTests.swift: 1| |// 2| |// ParseObjectBatchTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 7/27/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |import XCTest 11| |@testable import ParseSwift 12| | 13| |class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_length 14| | 15| | struct GameScore: ParseObject { 16| | //: Those are required for Object 17| | var objectId: String? 18| | var createdAt: Date? 19| | var updatedAt: Date? 20| | var ACL: ParseACL? 21| | 22| | //: Your own properties 23| | var score: Int 24| | 25| | //: a custom initializer 26| 18| init(score: Int) { 27| 18| self.score = score 28| 18| } 29| | } 30| | 31| 9| override func setUp() { 32| 9| super.setUp() 33| 9| guard let url = URL(string: "https://localhost:1337/1") else { 34| 0| XCTFail("Should create valid URL") 35| 0| return 36| 9| } 37| 9| ParseSwift.initialize(applicationId: "applicationId", 38| 9| clientKey: "clientKey", 39| 9| masterKey: "masterKey", 40| 9| serverURL: url) 41| 9| } 42| | 43| 9| override func tearDown() { 44| 9| super.tearDown() 45| 9| MockURLProtocol.removeAll() 46| 9| try? ParseStorage.shared.secureStore.deleteAll() 47| 9| } 48| | 49| 1| func testSaveAll() { // swiftlint:disable:this function_body_length cyclomatic_complexity 50| 1| let score = GameScore(score: 10) 51| 1| let score2 = GameScore(score: 20) 52| 1| 53| 1| var scoreOnServer = score 54| 1| scoreOnServer.objectId = "yarr" 55| 1| scoreOnServer.createdAt = Date() 56| 1| scoreOnServer.updatedAt = scoreOnServer.createdAt 57| 1| scoreOnServer.ACL = nil 58| 1| 59| 1| var scoreOnServer2 = score2 60| 1| scoreOnServer2.objectId = "yolo" 61| 1| scoreOnServer2.createdAt = Date() 62| 1| scoreOnServer2.updatedAt = scoreOnServer2.createdAt 63| 1| scoreOnServer2.ACL = nil 64| 1| 65| 1| let response = [BatchResponseItem(success: scoreOnServer, error: nil), 66| 1| BatchResponseItem(success: scoreOnServer2, error: nil)] 67| 1| let encoded: Data! 68| 1| do { 69| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) 70| 1| //Get dates in correct format from ParseDecoding strategy 71| 1| let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 72| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) 73| 1| let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) 74| 1| scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) 75| 1| 76| 1| } catch { 77| 0| XCTFail("Should have encoded/decoded. Error \(error)") 78| 0| return 79| 1| } 80| 2| MockURLProtocol.mockRequests { _ in 81| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 82| 2| } 83| 1| 84| 1| do { 85| 1| let saved = try [score, score2].saveAll() 86| 1| 87| 1| XCTAssertEqual(saved.count, 2) 88| 1| switch saved[0] { 89| 1| 90| 1| case .success(let first): 91| 1| XCTAssert(first.hasSameObjectId(as: scoreOnServer)) 92| 1| guard let savedCreatedAt = first.createdAt, 93| 1| let savedUpdatedAt = first.updatedAt else { 94| 0| XCTFail("Should unwrap dates") 95| 0| return 96| 1| } 97| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 98| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 99| 0| XCTFail("Should unwrap dates") 100| 0| return 101| 1| } 102| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 103| 1| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 104| 1| XCTAssertNil(first.ACL) 105| 1| case .failure(let error): 106| 0| XCTFail(error.localizedDescription) 107| 1| } 108| 1| 109| 1| switch saved[1] { 110| 1| 111| 1| case .success(let second): 112| 1| XCTAssert(second.hasSameObjectId(as: scoreOnServer2)) 113| 1| guard let savedCreatedAt = second.createdAt, 114| 1| let savedUpdatedAt = second.updatedAt else { 115| 0| XCTFail("Should unwrap dates") 116| 0| return 117| 1| } 118| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 119| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 120| 0| XCTFail("Should unwrap dates") 121| 0| return 122| 1| } 123| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 124| 1| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 125| 1| XCTAssertNil(second.ACL) 126| 1| case .failure(let error): 127| 0| XCTFail(error.localizedDescription) 128| 1| } 129| 1| 130| 1| } catch { 131| 0| XCTFail(error.localizedDescription) 132| 1| } 133| 1| 134| 1| do { 135| 1| let saved = try [score, score2].saveAll(options: [.installationId("hello")]) 136| 1| XCTAssertEqual(saved.count, 2) 137| 1| switch saved[0] { 138| 1| 139| 1| case .success(let first): 140| 1| XCTAssert(first.hasSameObjectId(as: scoreOnServer)) 141| 1| guard let savedCreatedAt = first.createdAt, 142| 1| let savedUpdatedAt = first.updatedAt else { 143| 0| XCTFail("Should unwrap dates") 144| 0| return 145| 1| } 146| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 147| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 148| 0| XCTFail("Should unwrap dates") 149| 0| return 150| 1| } 151| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 152| 1| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 153| 1| XCTAssertNil(first.ACL) 154| 1| case .failure(let error): 155| 0| XCTFail(error.localizedDescription) 156| 1| } 157| 1| 158| 1| switch saved[1] { 159| 1| 160| 1| case .success(let second): 161| 1| XCTAssert(second.hasSameObjectId(as: scoreOnServer2)) 162| 1| guard let savedCreatedAt = second.createdAt, 163| 1| let savedUpdatedAt = second.updatedAt else { 164| 0| XCTFail("Should unwrap dates") 165| 0| return 166| 1| } 167| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 168| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 169| 0| XCTFail("Should unwrap dates") 170| 0| return 171| 1| } 172| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 173| 1| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 174| 1| XCTAssertNil(second.ACL) 175| 1| case .failure(let error): 176| 0| XCTFail(error.localizedDescription) 177| 1| } 178| 1| } catch { 179| 0| XCTFail(error.localizedDescription) 180| 1| } 181| 1| } 182| | 183| 1| func testSaveAllErrorIncorrectServerResponse() { 184| 1| let score = GameScore(score: 10) 185| 1| let score2 = GameScore(score: 20) 186| 1| 187| 1| var scoreOnServer = score 188| 1| scoreOnServer.objectId = "yarr" 189| 1| scoreOnServer.createdAt = Date() 190| 1| scoreOnServer.updatedAt = Date() 191| 1| scoreOnServer.ACL = nil 192| 1| 193| 1| var scoreOnServer2 = score2 194| 1| scoreOnServer2.objectId = "yolo" 195| 1| scoreOnServer2.createdAt = Date() 196| 1| scoreOnServer2.updatedAt = Date() 197| 1| scoreOnServer2.ACL = nil 198| 1| 199| 2| MockURLProtocol.mockRequests { _ in 200| 2| do { 201| 2| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode([scoreOnServer, scoreOnServer2]) 202| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 203| 2| } catch { 204| 0| return nil 205| 0| } 206| 0| } 207| 1| 208| 1| do { 209| 1| let saved = try [score, score2].saveAll() 210| 1| 211| 1| XCTAssertEqual(saved.count, 2) 212| 1| XCTAssertThrowsError(try saved[0].get()) 213| 1| XCTAssertThrowsError(try saved[1].get()) 214| 1| 215| 1| } catch { 216| 0| XCTFail(error.localizedDescription) 217| 1| } 218| 1| 219| 1| do { 220| 1| let saved = try [score, score2].saveAll(options: [.useMasterKey]) 221| 1| 222| 1| XCTAssertEqual(saved.count, 2) 223| 1| XCTAssertThrowsError(try saved[0].get()) 224| 1| XCTAssertThrowsError(try saved[1].get()) 225| 1| 226| 1| } catch { 227| 0| XCTFail(error.localizedDescription) 228| 1| } 229| 1| } 230| | 231| 1| func testUpdateAll() { // swiftlint:disable:this function_body_length cyclomatic_complexity 232| 1| var score = GameScore(score: 10) 233| 1| score.objectId = "yarr" 234| 1| score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 235| 1| score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 236| 1| score.ACL = nil 237| 1| 238| 1| var score2 = GameScore(score: 20) 239| 1| score2.objectId = "yolo" 240| 1| score2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 241| 1| score2.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 242| 1| score2.ACL = nil 243| 1| 244| 1| var scoreOnServer = score 245| 1| scoreOnServer.updatedAt = Date() 246| 1| var scoreOnServer2 = score2 247| 1| scoreOnServer2.updatedAt = Date() 248| 1| 249| 1| let response = [BatchResponseItem(success: scoreOnServer, error: nil), 250| 1| BatchResponseItem(success: scoreOnServer2, error: nil)] 251| 1| let encoded: Data! 252| 1| do { 253| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) 254| 1| //Get dates in correct format from ParseDecoding strategy 255| 1| let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 256| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) 257| 1| let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) 258| 1| scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) 259| 1| 260| 1| } catch { 261| 0| XCTFail("Should have encoded/decoded. Error \(error)") 262| 0| return 263| 1| } 264| 2| MockURLProtocol.mockRequests { _ in 265| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 266| 2| } 267| 1| 268| 1| do { 269| 1| let saved = try [score, score2].saveAll() 270| 1| 271| 1| XCTAssertEqual(saved.count, 2) 272| 1| switch saved[0] { 273| 1| 274| 1| case .success(let first): 275| 1| 276| 1| guard let savedCreatedAt = first.createdAt, 277| 1| let savedUpdatedAt = first.updatedAt else { 278| 0| XCTFail("Should unwrap dates") 279| 0| return 280| 1| } 281| 1| guard let originalCreatedAt = score.createdAt, 282| 1| let originalUpdatedAt = score.updatedAt else { 283| 0| XCTFail("Should unwrap dates") 284| 0| return 285| 1| } 286| 1| 287| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 288| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 289| 1| XCTAssertNil(first.ACL) 290| 1| 291| 1| case .failure(let error): 292| 0| XCTFail(error.localizedDescription) 293| 1| } 294| 1| 295| 1| switch saved[1] { 296| 1| 297| 1| case .success(let second): 298| 1| 299| 1| guard let savedCreatedAt2 = second.createdAt, 300| 1| let savedUpdatedAt2 = second.updatedAt else { 301| 0| XCTFail("Should unwrap dates") 302| 0| return 303| 1| } 304| 1| guard let originalCreatedAt2 = score2.createdAt, 305| 1| let originalUpdatedAt2 = score2.updatedAt else { 306| 0| XCTFail("Should unwrap dates") 307| 0| return 308| 1| } 309| 1| 310| 1| XCTAssertEqual(savedCreatedAt2, originalCreatedAt2) 311| 1| XCTAssertGreaterThan(savedUpdatedAt2, originalUpdatedAt2) 312| 1| XCTAssertNil(second.ACL) 313| 1| 314| 1| case .failure(let error): 315| 0| XCTFail(error.localizedDescription) 316| 1| } 317| 1| 318| 1| } catch { 319| 0| XCTFail(error.localizedDescription) 320| 1| } 321| 1| 322| 1| do { 323| 1| let saved = try [score, score2].saveAll(options: [.useMasterKey]) 324| 1| XCTAssertEqual(saved.count, 2) 325| 1| 326| 1| switch saved[0] { 327| 1| 328| 1| case .success(let first): 329| 1| guard let savedCreatedAt = first.createdAt, 330| 1| let savedUpdatedAt = first.updatedAt else { 331| 0| XCTFail("Should unwrap dates") 332| 0| return 333| 1| } 334| 1| guard let originalCreatedAt = score.createdAt, 335| 1| let originalUpdatedAt = score.updatedAt else { 336| 0| XCTFail("Should unwrap dates") 337| 0| return 338| 1| } 339| 1| 340| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 341| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 342| 1| XCTAssertNil(first.ACL) 343| 1| case .failure(let error): 344| 0| XCTFail(error.localizedDescription) 345| 1| } 346| 1| 347| 1| switch saved[1] { 348| 1| 349| 1| case .success(let second): 350| 1| guard let savedCreatedAt2 = second.createdAt, 351| 1| let savedUpdatedAt2 = second.updatedAt else { 352| 0| XCTFail("Should unwrap dates") 353| 0| return 354| 1| } 355| 1| guard let originalCreatedAt2 = score2.createdAt, 356| 1| let originalUpdatedAt2 = score2.updatedAt else { 357| 0| XCTFail("Should unwrap dates") 358| 0| return 359| 1| } 360| 1| /*Date's are not exactly as their original because the URLMocking doesn't use the same dateEncoding 361| 1| strategy, so we only compare the day*/ 362| 1| XCTAssertTrue(Calendar.current.isDate(savedCreatedAt2, 363| 1| equalTo: originalCreatedAt2, toGranularity: .day)) 364| 1| XCTAssertGreaterThan(savedUpdatedAt2, originalUpdatedAt2) 365| 1| XCTAssertNil(second.ACL) 366| 1| case .failure(let error): 367| 0| XCTFail(error.localizedDescription) 368| 1| } 369| 1| 370| 1| } catch { 371| 0| XCTFail(error.localizedDescription) 372| 1| } 373| 1| } 374| | 375| 1| func testUpdateAllErrorIncorrectServerResponse() { 376| 1| var score = GameScore(score: 10) 377| 1| score.objectId = "yarr" 378| 1| score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 379| 1| score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 380| 1| score.ACL = nil 381| 1| 382| 1| var score2 = GameScore(score: 20) 383| 1| score2.objectId = "yolo" 384| 1| score2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 385| 1| score2.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 386| 1| score2.ACL = nil 387| 1| 388| 1| var scoreOnServer = score 389| 1| scoreOnServer.updatedAt = Date() 390| 1| var scoreOnServer2 = score2 391| 1| scoreOnServer2.updatedAt = Date() 392| 1| 393| 2| MockURLProtocol.mockRequests { _ in 394| 2| do { 395| 2| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode([scoreOnServer, scoreOnServer2]) 396| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 397| 2| } catch { 398| 0| return nil 399| 0| } 400| 0| } 401| 1| do { 402| 1| let saved = try [score, score2].saveAll() 403| 1| XCTAssertEqual(saved.count, 2) 404| 1| XCTAssertThrowsError(try saved[0].get()) 405| 1| XCTAssertThrowsError(try saved[1].get()) 406| 1| 407| 1| } catch { 408| 0| XCTFail(error.localizedDescription) 409| 1| } 410| 1| 411| 1| do { 412| 1| let saved = try [score, score2].saveAll(options: [.useMasterKey]) 413| 1| 414| 1| XCTAssertEqual(saved.count, 2) 415| 1| XCTAssertThrowsError(try saved[0].get()) 416| 1| XCTAssertThrowsError(try saved[1].get()) 417| 1| 418| 1| } catch { 419| 0| XCTFail(error.localizedDescription) 420| 1| } 421| 1| } 422| | 423| 1| func testSaveAllMixed() { // swiftlint:disable:this function_body_length cyclomatic_complexity 424| 1| let score = GameScore(score: 10) 425| 1| var score2 = GameScore(score: 20) 426| 1| score2.objectId = "yolo" 427| 1| score2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 428| 1| score2.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 429| 1| score2.ACL = nil 430| 1| 431| 1| var scoreOnServer = score 432| 1| scoreOnServer.objectId = "yarr" 433| 1| scoreOnServer.createdAt = Date() 434| 1| scoreOnServer.updatedAt = scoreOnServer.createdAt 435| 1| scoreOnServer.ACL = nil 436| 1| 437| 1| var scoreOnServer2 = score2 438| 1| scoreOnServer2.updatedAt = Date() 439| 1| 440| 1| let response = [BatchResponseItem(success: scoreOnServer, error: nil), 441| 1| BatchResponseItem(success: scoreOnServer2, error: nil)] 442| 1| 443| 1| let encoded: Data! 444| 1| do { 445| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) 446| 1| //Get dates in correct format from ParseDecoding strategy 447| 1| let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 448| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) 449| 1| let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) 450| 1| scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) 451| 1| 452| 1| } catch { 453| 0| XCTFail("Should have encoded/decoded. Error \(error)") 454| 0| return 455| 1| } 456| 2| MockURLProtocol.mockRequests { _ in 457| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 458| 2| } 459| 1| 460| 1| do { 461| 1| let saved = try [score, score2].saveAll() 462| 1| 463| 1| XCTAssertEqual(saved.count, 2) 464| 1| switch saved[0] { 465| 1| 466| 1| case .success(let first): 467| 1| XCTAssert(first.hasSameObjectId(as: scoreOnServer)) 468| 1| guard let savedCreatedAt = first.createdAt, 469| 1| let savedUpdatedAt = first.updatedAt else { 470| 0| XCTFail("Should unwrap dates") 471| 0| return 472| 1| } 473| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 474| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 475| 0| XCTFail("Should unwrap dates") 476| 0| return 477| 1| } 478| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 479| 1| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 480| 1| XCTAssertNil(first.ACL) 481| 1| case .failure(let error): 482| 0| XCTFail(error.localizedDescription) 483| 1| } 484| 1| 485| 1| switch saved[1] { 486| 1| 487| 1| case .success(let second): 488| 1| guard let savedCreatedAt = second.createdAt, 489| 1| let savedUpdatedAt = second.updatedAt else { 490| 0| XCTFail("Should unwrap dates") 491| 0| return 492| 1| } 493| 1| guard let originalCreatedAt = score2.createdAt, 494| 1| let originalUpdatedAt = score2.updatedAt else { 495| 0| XCTFail("Should unwrap dates") 496| 0| return 497| 1| } 498| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 499| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 500| 1| XCTAssertNil(second.ACL) 501| 1| case .failure(let error): 502| 0| XCTFail(error.localizedDescription) 503| 1| } 504| 1| 505| 1| } catch { 506| 0| XCTFail(error.localizedDescription) 507| 1| } 508| 1| 509| 1| do { 510| 1| let saved = try [score, score2].saveAll(options: [.useMasterKey]) 511| 1| XCTAssertEqual(saved.count, 2) 512| 1| switch saved[0] { 513| 1| 514| 1| case .success(let first): 515| 1| XCTAssertNotNil(first.createdAt) 516| 1| XCTAssertNotNil(first.updatedAt) 517| 1| XCTAssertNil(first.ACL) 518| 1| case .failure(let error): 519| 0| XCTFail(error.localizedDescription) 520| 1| } 521| 1| 522| 1| switch saved[1] { 523| 1| 524| 1| case .success(let second): 525| 1| XCTAssertNotNil(second.createdAt) 526| 1| XCTAssertNotNil(second.updatedAt) 527| 1| XCTAssertNil(second.ACL) 528| 1| case .failure(let error): 529| 0| XCTFail(error.localizedDescription) 530| 1| } 531| 1| } catch { 532| 0| XCTFail(error.localizedDescription) 533| 1| } 534| 1| } 535| | 536| | func saveAllAsync(scores: [GameScore], // swiftlint:disable:this function_body_length cyclomatic_complexity 537| 101| scoresOnServer: [GameScore], callbackQueue: DispatchQueue) { 538| 101| 539| 101| let expectation1 = XCTestExpectation(description: "Save object1") 540| 101| guard let scoreOnServer = scoresOnServer.first, 541| 101| let scoreOnServer2 = scoresOnServer.last else { 542| 0| XCTFail("Should unwrap") 543| 0| return 544| 101| } 545| 101| 546| 101| scores.saveAll(options: [], callbackQueue: callbackQueue) { result in 547| 101| 548| 101| switch result { 549| 101| 550| 101| case .success(let saved): 551| 101| XCTAssertEqual(saved.count, 2) 552| 101| guard let firstObject = saved.first, 553| 101| let secondObject = saved.last else { 554| 0| XCTFail("Should unwrap") 555| 0| expectation1.fulfill() 556| 0| return 557| 101| } 558| 101| 559| 101| switch firstObject { 560| 101| 561| 101| case .success(let first): 562| 101| XCTAssert(first.hasSameObjectId(as: scoreOnServer)) 563| 101| guard let savedCreatedAt = first.createdAt, 564| 101| let savedUpdatedAt = first.updatedAt else { 565| 0| XCTFail("Should unwrap dates") 566| 0| expectation1.fulfill() 567| 0| return 568| 101| } 569| 101| guard let originalCreatedAt = scoreOnServer.createdAt, 570| 101| let originalUpdatedAt = scoreOnServer.updatedAt else { 571| 0| XCTFail("Should unwrap dates") 572| 0| expectation1.fulfill() 573| 0| return 574| 101| } 575| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 576| 101| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 577| 101| XCTAssertNil(first.ACL) 578| 101| 579| 101| case .failure(let error): 580| 0| XCTFail(error.localizedDescription) 581| 101| } 582| 101| 583| 101| switch secondObject { 584| 101| 585| 101| case .success(let second): 586| 101| XCTAssert(second.hasSameObjectId(as: scoreOnServer2)) 587| 101| guard let savedCreatedAt = second.createdAt, 588| 101| let savedUpdatedAt = second.updatedAt else { 589| 0| XCTFail("Should unwrap dates") 590| 0| expectation1.fulfill() 591| 0| return 592| 101| } 593| 101| guard let originalCreatedAt = scoreOnServer2.createdAt, 594| 101| let originalUpdatedAt = scoreOnServer2.updatedAt else { 595| 0| XCTFail("Should unwrap dates") 596| 0| expectation1.fulfill() 597| 0| return 598| 101| } 599| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 600| 101| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 601| 101| XCTAssertNil(second.ACL) 602| 101| 603| 101| case .failure(let error): 604| 0| XCTFail(error.localizedDescription) 605| 101| } 606| 101| 607| 101| case .failure(let error): 608| 0| XCTFail(error.localizedDescription) 609| 101| } 610| 101| expectation1.fulfill() 611| 101| } 612| 101| 613| 101| let expectation2 = XCTestExpectation(description: "Save object2") 614| 101| scores.saveAll(options: [.useMasterKey], callbackQueue: callbackQueue) { result in 615| 101| 616| 101| switch result { 617| 101| 618| 101| case .success(let saved): 619| 101| XCTAssertEqual(saved.count, 2) 620| 101| 621| 101| guard let firstObject = saved.first, 622| 101| let secondObject = saved.last else { 623| 0| XCTFail("Should unwrap") 624| 0| expectation2.fulfill() 625| 0| return 626| 101| } 627| 101| 628| 101| switch firstObject { 629| 101| 630| 101| case .success(let first): 631| 101| guard let savedCreatedAt = first.createdAt, 632| 101| let savedUpdatedAt = first.updatedAt else { 633| 0| XCTFail("Should unwrap dates") 634| 0| expectation2.fulfill() 635| 0| return 636| 101| } 637| 101| guard let originalCreatedAt = scoreOnServer.createdAt, 638| 101| let originalUpdatedAt = scoreOnServer.updatedAt else { 639| 0| XCTFail("Should unwrap dates") 640| 0| expectation2.fulfill() 641| 0| return 642| 101| } 643| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 644| 101| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 645| 101| XCTAssertNil(first.ACL) 646| 101| case .failure(let error): 647| 0| XCTFail(error.localizedDescription) 648| 101| } 649| 101| 650| 101| switch secondObject { 651| 101| 652| 101| case .success(let second): 653| 101| guard let savedCreatedAt = second.createdAt, 654| 101| let savedUpdatedAt = second.updatedAt else { 655| 0| XCTFail("Should unwrap dates") 656| 0| expectation2.fulfill() 657| 0| return 658| 101| } 659| 101| guard let originalCreatedAt = scoreOnServer2.createdAt, 660| 101| let originalUpdatedAt = scoreOnServer2.updatedAt else { 661| 0| XCTFail("Should unwrap dates") 662| 0| expectation2.fulfill() 663| 0| return 664| 101| } 665| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 666| 101| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 667| 101| XCTAssertNil(second.ACL) 668| 101| case .failure(let error): 669| 0| XCTFail(error.localizedDescription) 670| 101| } 671| 101| 672| 101| case .failure(let error): 673| 0| XCTFail(error.localizedDescription) 674| 101| } 675| 101| expectation2.fulfill() 676| 101| } 677| 101| wait(for: [expectation1, expectation2], timeout: 10.0) 678| 101| } 679| | 680| 1| func testThreadSafeSaveAllAsync() { 681| 1| let score = GameScore(score: 10) 682| 1| let score2 = GameScore(score: 20) 683| 1| 684| 1| var scoreOnServer = score 685| 1| scoreOnServer.objectId = "yarr" 686| 1| scoreOnServer.createdAt = Date() 687| 1| scoreOnServer.updatedAt = Date() 688| 1| scoreOnServer.ACL = nil 689| 1| 690| 1| var scoreOnServer2 = score2 691| 1| scoreOnServer2.objectId = "yolo" 692| 1| scoreOnServer2.createdAt = Date() 693| 1| scoreOnServer2.updatedAt = Date() 694| 1| scoreOnServer2.ACL = nil 695| 1| 696| 1| let response = [BatchResponseItem(success: scoreOnServer, error: nil), 697| 1| BatchResponseItem(success: scoreOnServer2, error: nil)] 698| 1| let encoded: Data! 699| 1| do { 700| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) 701| 1| //Get dates in correct format from ParseDecoding strategy 702| 1| let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 703| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) 704| 1| let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) 705| 1| scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) 706| 1| 707| 1| } catch { 708| 0| XCTFail("Should have encoded/decoded. Error \(error)") 709| 0| return 710| 1| } 711| 200| MockURLProtocol.mockRequests { _ in 712| 200| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 713| 200| } 714| 1| 715| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 716| 100| self.saveAllAsync(scores: [score, score2], scoresOnServer: [scoreOnServer, scoreOnServer2], 717| 100| callbackQueue: .global(qos: .background)) 718| 100| } 719| 1| } 720| | 721| 1| func testSaveAllAsyncMainQueue() { 722| 1| let score = GameScore(score: 10) 723| 1| let score2 = GameScore(score: 20) 724| 1| 725| 1| var scoreOnServer = score 726| 1| scoreOnServer.objectId = "yarr" 727| 1| scoreOnServer.createdAt = Date() 728| 1| scoreOnServer.updatedAt = scoreOnServer.createdAt 729| 1| scoreOnServer.ACL = nil 730| 1| 731| 1| var scoreOnServer2 = score2 732| 1| scoreOnServer2.objectId = "yolo" 733| 1| scoreOnServer2.createdAt = Date() 734| 1| scoreOnServer2.updatedAt = scoreOnServer2.createdAt 735| 1| scoreOnServer2.ACL = nil 736| 1| 737| 1| let response = [BatchResponseItem(success: scoreOnServer, error: nil), 738| 1| BatchResponseItem(success: scoreOnServer2, error: nil)] 739| 1| let encoded: Data! 740| 1| do { 741| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) 742| 1| //Get dates in correct format from ParseDecoding strategy 743| 1| let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 744| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) 745| 1| let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) 746| 1| scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) 747| 1| 748| 1| } catch { 749| 0| XCTFail("Should have encoded/decoded. Error \(error)") 750| 0| return 751| 1| } 752| 2| MockURLProtocol.mockRequests { _ in 753| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 754| 2| } 755| 1| self.saveAllAsync(scores: [score, score2], scoresOnServer: [scoreOnServer, scoreOnServer2], 756| 1| callbackQueue: .main) 757| 1| } 758| | 759| | /* Note, the current batchCommand for updateAll returns the original object that was updated as 760| | opposed to the latestUpdated. The objective c one just returns true/false */ 761| | func updateAllAsync(scores: [GameScore], // swiftlint:disable:this function_body_length cyclomatic_complexity 762| 101| callbackQueue: DispatchQueue) { 763| 101| 764| 101| let expectation1 = XCTestExpectation(description: "Update object1") 765| 101| 766| 101| scores.saveAll(options: [], callbackQueue: callbackQueue) { result in 767| 101| 768| 101| switch result { 769| 101| 770| 101| case .success(let saved): 771| 101| guard let firstObject = saved.first, 772| 101| let secondObject = saved.last else { 773| 0| XCTFail("Should unwrap") 774| 0| expectation1.fulfill() 775| 0| return 776| 101| } 777| 101| 778| 101| switch firstObject { 779| 101| 780| 101| case .success(let first): 781| 101| guard let savedCreatedAt = first.createdAt, 782| 101| let savedUpdatedAt = first.updatedAt else { 783| 0| XCTFail("Should unwrap dates") 784| 0| expectation1.fulfill() 785| 0| return 786| 101| } 787| 101| guard let originalCreatedAt = scores.first?.createdAt, 788| 101| let originalUpdatedAt = scores.first?.updatedAt else { 789| 0| XCTFail("Should unwrap dates") 790| 0| expectation1.fulfill() 791| 0| return 792| 101| } 793| 101| 794| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 795| 101| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 796| 101| XCTAssertNil(first.ACL) 797| 101| case .failure(let error): 798| 0| XCTFail(error.localizedDescription) 799| 101| } 800| 101| 801| 101| switch secondObject { 802| 101| 803| 101| case .success(let second): 804| 101| guard let savedCreatedAt2 = second.createdAt, 805| 101| let savedUpdatedAt2 = second.updatedAt else { 806| 0| XCTFail("Should unwrap dates") 807| 0| expectation1.fulfill() 808| 0| return 809| 101| } 810| 101| guard let originalCreatedAt2 = scores.last?.createdAt, 811| 101| let originalUpdatedAt2 = scores.last?.updatedAt else { 812| 0| XCTFail("Should unwrap dates") 813| 0| expectation1.fulfill() 814| 0| return 815| 101| } 816| 101| 817| 101| XCTAssertEqual(savedCreatedAt2, originalCreatedAt2) 818| 101| XCTAssertGreaterThan(savedUpdatedAt2, 819| 101| originalUpdatedAt2) 820| 101| XCTAssertNil(second.ACL) 821| 101| case .failure(let error): 822| 0| XCTFail(error.localizedDescription) 823| 101| } 824| 101| 825| 101| case .failure(let error): 826| 0| XCTFail(error.localizedDescription) 827| 101| } 828| 101| expectation1.fulfill() 829| 101| } 830| 101| 831| 101| let expectation2 = XCTestExpectation(description: "Update object2") 832| 101| scores.saveAll(options: [.useMasterKey], callbackQueue: callbackQueue) { result in 833| 101| 834| 101| switch result { 835| 101| 836| 101| case .success(let saved): 837| 101| guard let firstObject = saved.first, 838| 101| let secondObject = saved.last else { 839| 0| expectation2.fulfill() 840| 0| XCTFail("Should unwrap") 841| 0| return 842| 101| } 843| 101| 844| 101| switch firstObject { 845| 101| 846| 101| case .success(let first): 847| 101| guard let savedCreatedAt = first.createdAt, 848| 101| let savedUpdatedAt = first.updatedAt else { 849| 0| XCTFail("Should unwrap dates") 850| 0| expectation2.fulfill() 851| 0| return 852| 101| } 853| 101| guard let originalCreatedAt = scores.first?.createdAt, 854| 101| let originalUpdatedAt = scores.first?.updatedAt else { 855| 0| XCTFail("Should unwrap dates") 856| 0| expectation2.fulfill() 857| 0| return 858| 101| } 859| 101| 860| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 861| 101| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 862| 101| XCTAssertNil(first.ACL) 863| 101| case .failure(let error): 864| 0| XCTFail(error.localizedDescription) 865| 101| } 866| 101| 867| 101| switch secondObject { 868| 101| 869| 101| case .success(let second): 870| 101| guard let savedCreatedAt2 = second.createdAt, 871| 101| let savedUpdatedAt2 = second.updatedAt else { 872| 0| XCTFail("Should unwrap dates") 873| 0| expectation2.fulfill() 874| 0| return 875| 101| } 876| 101| guard let originalCreatedAt2 = scores.last?.createdAt, 877| 101| let originalUpdatedAt2 = scores.last?.updatedAt else { 878| 0| XCTFail("Should unwrap dates") 879| 0| expectation2.fulfill() 880| 0| return 881| 101| } 882| 101| 883| 101| XCTAssertEqual(savedCreatedAt2, originalCreatedAt2) 884| 101| XCTAssertGreaterThan(savedUpdatedAt2, 885| 101| originalUpdatedAt2) 886| 101| XCTAssertNil(second.ACL) 887| 101| case .failure(let error): 888| 0| XCTFail(error.localizedDescription) 889| 101| } 890| 101| 891| 101| case .failure(let error): 892| 0| XCTFail(error.localizedDescription) 893| 101| } 894| 101| expectation2.fulfill() 895| 101| } 896| 101| wait(for: [expectation1, expectation2], timeout: 10.0) 897| 101| } 898| | 899| 1| func testThreadSafeUpdateAllAsync() { 900| 1| var score = GameScore(score: 10) 901| 1| score.objectId = "yarr" 902| 1| score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 903| 1| score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 904| 1| score.ACL = nil 905| 1| 906| 1| var score2 = GameScore(score: 20) 907| 1| score2.objectId = "yolo" 908| 1| score2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 909| 1| score2.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 910| 1| score2.ACL = nil 911| 1| 912| 1| var scoreOnServer = score 913| 1| scoreOnServer.updatedAt = Date() 914| 1| var scoreOnServer2 = score2 915| 1| scoreOnServer2.updatedAt = Date() 916| 1| 917| 1| let response = [BatchResponseItem(success: scoreOnServer, error: nil), 918| 1| BatchResponseItem(success: scoreOnServer2, error: nil)] 919| 1| 920| 1| let encoded: Data! 921| 1| do { 922| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) 923| 1| //Get dates in correct format from ParseDecoding strategy 924| 1| let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 925| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) 926| 1| let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) 927| 1| scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) 928| 1| 929| 1| } catch { 930| 0| XCTFail("Should have encoded/decoded. Error \(error)") 931| 0| return 932| 1| } 933| 200| MockURLProtocol.mockRequests { _ in 934| 200| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 935| 200| } 936| 1| 937| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 938| 100| self.updateAllAsync(scores: [score, score2], callbackQueue: .global(qos: .background)) 939| 100| } 940| 1| } 941| | 942| 1| func testUpdateAllAsyncMainQueue() { 943| 1| var score = GameScore(score: 10) 944| 1| score.objectId = "yarr" 945| 1| score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 946| 1| score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 947| 1| score.ACL = nil 948| 1| 949| 1| var score2 = GameScore(score: 20) 950| 1| score2.objectId = "yolo" 951| 1| score2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 952| 1| score2.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 953| 1| score2.ACL = nil 954| 1| 955| 1| var scoreOnServer = score 956| 1| scoreOnServer.updatedAt = Date() 957| 1| var scoreOnServer2 = score2 958| 1| scoreOnServer2.updatedAt = Date() 959| 1| 960| 1| let response = [BatchResponseItem(success: scoreOnServer, error: nil), 961| 1| BatchResponseItem(success: scoreOnServer2, error: nil)] 962| 1| 963| 1| let encoded: Data! 964| 1| do { 965| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) 966| 1| //Get dates in correct format from ParseDecoding strategy 967| 1| let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 968| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) 969| 1| let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) 970| 1| scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) 971| 1| 972| 1| } catch { 973| 0| XCTFail("Should have encoded/decoded. Error \(error)") 974| 0| return 975| 1| } 976| 2| MockURLProtocol.mockRequests { _ in 977| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 978| 2| } 979| 1| self.updateAllAsync(scores: [score, score2], callbackQueue: .main) 980| 1| } 981| |} // swiftlint:disable:this file_length /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/ParseObjectCommandTests.swift: 1| |// 2| |// ParseObjectCommandTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 7/19/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |import XCTest 11| |@testable import ParseSwift 12| | 13| |class ParseObjectCommandTests: XCTestCase { // swiftlint:disable:this type_body_length 14| | 15| | struct GameScore: ParseObject { 16| | //: Those are required for Object 17| | var objectId: String? 18| | var createdAt: Date? 19| | var updatedAt: Date? 20| | var ACL: ParseACL? 21| | 22| | //: Your own properties 23| | var score: Int 24| | 25| | //: a custom initializer 26| 12| init(score: Int) { 27| 12| self.score = score 28| 12| } 29| | } 30| | 31| 12| override func setUp() { 32| 12| super.setUp() 33| 12| guard let url = URL(string: "https://localhost:1337/1") else { 34| 0| XCTFail("Should create valid URL") 35| 0| return 36| 12| } 37| 12| ParseSwift.initialize(applicationId: "applicationId", 38| 12| clientKey: "clientKey", 39| 12| masterKey: "masterKey", 40| 12| serverURL: url) 41| 12| } 42| | 43| 12| override func tearDown() { 44| 12| super.tearDown() 45| 12| MockURLProtocol.removeAll() 46| 12| try? ParseStorage.shared.secureStore.deleteAll() 47| 12| } 48| | 49| 1| func testFetchCommand() { 50| 1| var score = GameScore(score: 10) 51| 1| let className = score.className 52| 1| let objectId = "yarr" 53| 1| score.objectId = objectId 54| 1| do { 55| 1| let command = try score.fetchCommand() 56| 1| XCTAssertNotNil(command) 57| 1| XCTAssertEqual(command.path.urlComponent, "/classes/\(className)/\(objectId)") 58| 1| XCTAssertEqual(command.method, API.Method.GET) 59| 1| XCTAssertNil(command.params) 60| 1| XCTAssertNil(command.body) 61| 1| XCTAssertNil(command.data) 62| 1| } catch { 63| 0| XCTFail(error.localizedDescription) 64| 1| } 65| 1| } 66| | 67| | // swiftlint:disable:next function_body_length 68| 1| func testFetch() { 69| 1| var score = GameScore(score: 10) 70| 1| let objectId = "yarr" 71| 1| score.objectId = objectId 72| 1| 73| 1| var scoreOnServer = score 74| 1| scoreOnServer.createdAt = Date() 75| 1| scoreOnServer.updatedAt = Date() 76| 1| scoreOnServer.ACL = nil 77| 1| let encoded: Data! 78| 1| do { 79| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 80| 1| //Get dates in correct format from ParseDecoding strategy 81| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 82| 1| } catch { 83| 0| XCTFail("Should encode/decode. Error \(error)") 84| 0| return 85| 1| } 86| 1| 87| 2| MockURLProtocol.mockRequests { _ in 88| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 89| 2| } 90| 1| do { 91| 1| let fetched = try score.fetch(options: []) 92| 1| XCTAssert(fetched.hasSameObjectId(as: scoreOnServer)) 93| 1| guard let fetchedCreatedAt = fetched.createdAt, 94| 1| let fetchedUpdatedAt = fetched.updatedAt else { 95| 0| XCTFail("Should unwrap dates") 96| 0| return 97| 1| } 98| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 99| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 100| 0| XCTFail("Should unwrap dates") 101| 0| return 102| 1| } 103| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 104| 1| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 105| 1| XCTAssertNil(fetched.ACL) 106| 1| } catch { 107| 0| XCTFail(error.localizedDescription) 108| 1| } 109| 1| 110| 1| do { 111| 1| let fetched = try score.fetch(options: [.useMasterKey]) 112| 1| XCTAssert(fetched.hasSameObjectId(as: scoreOnServer)) 113| 1| guard let fetchedCreatedAt = fetched.createdAt, 114| 1| let fetchedUpdatedAt = fetched.updatedAt else { 115| 0| XCTFail("Should unwrap dates") 116| 0| return 117| 1| } 118| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 119| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 120| 0| XCTFail("Should unwrap dates") 121| 0| return 122| 1| } 123| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 124| 1| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 125| 1| XCTAssertNil(fetched.ACL) 126| 1| } catch { 127| 0| XCTFail(error.localizedDescription) 128| 1| } 129| 1| } 130| | 131| | // swiftlint:disable:next function_body_length 132| 101| func fetchAsync(score: GameScore, scoreOnServer: GameScore, callbackQueue: DispatchQueue) { 133| 101| 134| 101| let expectation1 = XCTestExpectation(description: "Fetch object1") 135| 101| score.fetch(options: [], callbackQueue: callbackQueue) { result in 136| 101| 137| 101| switch result { 138| 101| case .success(let fetched): 139| 101| XCTAssert(fetched.hasSameObjectId(as: scoreOnServer)) 140| 101| guard let fetchedCreatedAt = fetched.createdAt, 141| 101| let fetchedUpdatedAt = fetched.updatedAt else { 142| 0| XCTFail("Should unwrap dates") 143| 0| expectation1.fulfill() 144| 0| return 145| 101| } 146| 101| guard let originalCreatedAt = scoreOnServer.createdAt, 147| 101| let originalUpdatedAt = scoreOnServer.updatedAt else { 148| 0| XCTFail("Should unwrap dates") 149| 0| expectation1.fulfill() 150| 0| return 151| 101| } 152| 101| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 153| 101| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 154| 101| XCTAssertNil(fetched.ACL) 155| 101| case .failure(let error): 156| 0| XCTFail(error.localizedDescription) 157| 101| } 158| 101| expectation1.fulfill() 159| 101| } 160| 101| 161| 101| let expectation2 = XCTestExpectation(description: "Fetch object2") 162| 101| score.fetch(options: [.useMasterKey], callbackQueue: callbackQueue) { result in 163| 101| 164| 101| switch result { 165| 101| case .success(let fetched): 166| 101| XCTAssert(fetched.hasSameObjectId(as: scoreOnServer)) 167| 101| guard let fetchedCreatedAt = fetched.createdAt, 168| 101| let fetchedUpdatedAt = fetched.updatedAt else { 169| 0| XCTFail("Should unwrap dates") 170| 0| expectation2.fulfill() 171| 0| return 172| 101| } 173| 101| guard let originalCreatedAt = scoreOnServer.createdAt, 174| 101| let originalUpdatedAt = scoreOnServer.updatedAt else { 175| 0| XCTFail("Should unwrap dates") 176| 0| expectation2.fulfill() 177| 0| return 178| 101| } 179| 101| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 180| 101| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 181| 101| XCTAssertNil(fetched.ACL) 182| 101| case .failure(let error): 183| 0| XCTFail(error.localizedDescription) 184| 101| } 185| 101| expectation2.fulfill() 186| 101| } 187| 101| wait(for: [expectation1, expectation2], timeout: 10.0) 188| 101| } 189| | 190| 1| func testThreadSafeFetchAsync() { 191| 1| var score = GameScore(score: 10) 192| 1| let objectId = "yarr" 193| 1| score.objectId = objectId 194| 1| 195| 1| var scoreOnServer = score 196| 1| scoreOnServer.createdAt = Date() 197| 1| scoreOnServer.updatedAt = Date() 198| 1| scoreOnServer.ACL = nil 199| 1| 200| 1| let encoded: Data! 201| 1| do { 202| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 203| 1| //Get dates in correct format from ParseDecoding strategy 204| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 205| 1| } catch { 206| 0| XCTFail("Should have encoded/decoded: Error: \(error)") 207| 0| return 208| 1| } 209| 1| 210| 200| MockURLProtocol.mockRequests { _ in 211| 200| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 212| 200| } 213| 1| 214| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 215| 100| self.fetchAsync(score: score, scoreOnServer: scoreOnServer, callbackQueue: .global(qos: .background)) 216| 100| } 217| 1| } 218| | 219| 1| func testFetchAsyncMainQueue() { 220| 1| var score = GameScore(score: 10) 221| 1| let objectId = "yarr" 222| 1| score.objectId = objectId 223| 1| 224| 1| var scoreOnServer = score 225| 1| scoreOnServer.createdAt = Date() 226| 1| scoreOnServer.updatedAt = Date() 227| 1| scoreOnServer.ACL = nil 228| 1| let encoded: Data! 229| 1| do { 230| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 231| 1| //Get dates in correct format from ParseDecoding strategy 232| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 233| 1| } catch { 234| 0| XCTFail("Should have encoded/decoded: Error: \(error)") 235| 0| return 236| 1| } 237| 1| 238| 2| MockURLProtocol.mockRequests { _ in 239| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 240| 2| } 241| 1| self.fetchAsync(score: score, scoreOnServer: scoreOnServer, callbackQueue: .main) 242| 1| } 243| | 244| 1| func testSaveCommand() { 245| 1| let score = GameScore(score: 10) 246| 1| let className = score.className 247| 1| 248| 1| let command = score.saveCommand() 249| 1| XCTAssertNotNil(command) 250| 1| XCTAssertEqual(command.path.urlComponent, "/classes/\(className)") 251| 1| XCTAssertEqual(command.method, API.Method.POST) 252| 1| XCTAssertNil(command.params) 253| 1| XCTAssertNotNil(command.body) 254| 1| XCTAssertNotNil(command.data) 255| 1| } 256| | 257| 1| func testUpdateCommand() { 258| 1| var score = GameScore(score: 10) 259| 1| let className = score.className 260| 1| let objectId = "yarr" 261| 1| score.objectId = objectId 262| 1| 263| 1| let command = score.saveCommand() 264| 1| XCTAssertNotNil(command) 265| 1| XCTAssertEqual(command.path.urlComponent, "/classes/\(className)/\(objectId)") 266| 1| XCTAssertEqual(command.method, API.Method.PUT) 267| 1| XCTAssertNil(command.params) 268| 1| XCTAssertNotNil(command.body) 269| 1| XCTAssertNotNil(command.data) 270| 1| } 271| | 272| 1| func testSave() { // swiftlint:disable:this function_body_length 273| 1| let score = GameScore(score: 10) 274| 1| 275| 1| var scoreOnServer = score 276| 1| scoreOnServer.objectId = "yarr" 277| 1| scoreOnServer.createdAt = Date() 278| 1| scoreOnServer.updatedAt = scoreOnServer.createdAt 279| 1| scoreOnServer.ACL = nil 280| 1| 281| 1| let encoded: Data! 282| 1| do { 283| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 284| 1| //Get dates in correct format from ParseDecoding strategy 285| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 286| 1| } catch { 287| 0| XCTFail("Should encode/decode. Error \(error)") 288| 0| return 289| 1| } 290| 1| 291| 2| MockURLProtocol.mockRequests { _ in 292| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 293| 2| } 294| 1| do { 295| 1| let saved = try score.save() 296| 1| XCTAssert(saved.hasSameObjectId(as: scoreOnServer)) 297| 1| guard let savedCreatedAt = saved.createdAt, 298| 1| let savedUpdatedAt = saved.updatedAt else { 299| 0| XCTFail("Should unwrap dates") 300| 0| return 301| 1| } 302| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 303| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 304| 0| XCTFail("Should unwrap dates") 305| 0| return 306| 1| } 307| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 308| 1| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 309| 1| XCTAssertNil(saved.ACL) 310| 1| } catch { 311| 0| XCTFail(error.localizedDescription) 312| 1| } 313| 1| 314| 1| do { 315| 1| let saved = try score.save(options: [.useMasterKey]) 316| 1| XCTAssert(saved.hasSameObjectId(as: scoreOnServer)) 317| 1| guard let savedCreatedAt = saved.createdAt, 318| 1| let savedUpdatedAt = saved.updatedAt else { 319| 0| XCTFail("Should unwrap dates") 320| 0| return 321| 1| } 322| 1| guard let originalCreatedAt = scoreOnServer.createdAt, 323| 1| let originalUpdatedAt = scoreOnServer.updatedAt else { 324| 0| XCTFail("Should unwrap dates") 325| 0| return 326| 1| } 327| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 328| 1| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 329| 1| XCTAssertNil(saved.ACL) 330| 1| } catch { 331| 0| XCTFail(error.localizedDescription) 332| 1| } 333| 1| } 334| | 335| 1| func testUpdate() { // swiftlint:disable:this function_body_length 336| 1| var score = GameScore(score: 10) 337| 1| score.objectId = "yarr" 338| 1| score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 339| 1| score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 340| 1| score.ACL = nil 341| 1| 342| 1| var scoreOnServer = score 343| 1| scoreOnServer.updatedAt = Date() 344| 1| 345| 1| let encoded: Data! 346| 1| do { 347| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 348| 1| //Get dates in correct format from ParseDecoding strategy 349| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 350| 1| } catch { 351| 0| XCTFail("Should encode/decode. Error \(error)") 352| 0| return 353| 1| } 354| 1| 355| 2| MockURLProtocol.mockRequests { _ in 356| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 357| 2| } 358| 1| do { 359| 1| let saved = try score.save() 360| 1| guard let savedCreatedAt = saved.createdAt, 361| 1| let savedUpdatedAt = saved.updatedAt else { 362| 0| XCTFail("Should unwrap dates") 363| 0| return 364| 1| } 365| 1| guard let originalCreatedAt = score.createdAt, 366| 1| let originalUpdatedAt = score.updatedAt else { 367| 0| XCTFail("Should unwrap dates") 368| 0| return 369| 1| } 370| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 371| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 372| 1| XCTAssertNil(saved.ACL) 373| 1| } catch { 374| 0| XCTFail(error.localizedDescription) 375| 1| } 376| 1| 377| 1| do { 378| 1| let saved = try score.save(options: [.useMasterKey]) 379| 1| guard let savedCreatedAt = saved.createdAt, 380| 1| let savedUpdatedAt = saved.updatedAt else { 381| 0| XCTFail("Should unwrap dates") 382| 0| return 383| 1| } 384| 1| guard let originalCreatedAt = score.createdAt, 385| 1| let originalUpdatedAt = score.updatedAt else { 386| 0| XCTFail("Should unwrap dates") 387| 0| return 388| 1| } 389| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 390| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 391| 1| XCTAssertNil(saved.ACL) 392| 1| } catch { 393| 0| XCTFail(error.localizedDescription) 394| 1| } 395| 1| } 396| | 397| | // swiftlint:disable:next function_body_length 398| 101| func saveAsync(score: GameScore, scoreOnServer: GameScore, callbackQueue: DispatchQueue) { 399| 101| 400| 101| let expectation1 = XCTestExpectation(description: "Save object1") 401| 101| 402| 101| score.save(options: [], callbackQueue: callbackQueue) { result in 403| 101| 404| 101| switch result { 405| 101| 406| 101| case .success(let saved): 407| 101| XCTAssert(saved.hasSameObjectId(as: scoreOnServer)) 408| 101| guard let savedCreatedAt = saved.createdAt, 409| 101| let savedUpdatedAt = saved.updatedAt else { 410| 0| XCTFail("Should unwrap dates") 411| 0| expectation1.fulfill() 412| 0| return 413| 101| } 414| 101| guard let originalCreatedAt = scoreOnServer.createdAt, 415| 101| let originalUpdatedAt = scoreOnServer.updatedAt else { 416| 0| XCTFail("Should unwrap dates") 417| 0| expectation1.fulfill() 418| 0| return 419| 101| } 420| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 421| 101| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 422| 101| XCTAssertNil(saved.ACL) 423| 101| case .failure(let error): 424| 0| XCTFail(error.localizedDescription) 425| 101| } 426| 101| expectation1.fulfill() 427| 101| } 428| 101| 429| 101| let expectation2 = XCTestExpectation(description: "Save object2") 430| 101| score.save(options: [.useMasterKey], callbackQueue: callbackQueue) { result in 431| 101| 432| 101| switch result { 433| 101| 434| 101| case .success(let saved): 435| 101| XCTAssert(saved.hasSameObjectId(as: scoreOnServer)) 436| 101| guard let savedCreatedAt = saved.createdAt, 437| 101| let savedUpdatedAt = saved.updatedAt else { 438| 0| XCTFail("Should unwrap dates") 439| 0| expectation2.fulfill() 440| 0| return 441| 101| } 442| 101| guard let originalCreatedAt = scoreOnServer.createdAt, 443| 101| let originalUpdatedAt = scoreOnServer.updatedAt else { 444| 0| XCTFail("Should unwrap dates") 445| 0| expectation2.fulfill() 446| 0| return 447| 101| } 448| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 449| 101| XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) 450| 101| XCTAssertNil(saved.ACL) 451| 101| case .failure(let error): 452| 0| XCTFail(error.localizedDescription) 453| 101| } 454| 101| expectation2.fulfill() 455| 101| } 456| 101| wait(for: [expectation1, expectation2], timeout: 10.0) 457| 101| } 458| | 459| 1| func testThreadSafeSaveAsync() { 460| 1| let score = GameScore(score: 10) 461| 1| 462| 1| var scoreOnServer = score 463| 1| scoreOnServer.objectId = "yarr" 464| 1| scoreOnServer.createdAt = Date() 465| 1| scoreOnServer.updatedAt = scoreOnServer.createdAt 466| 1| scoreOnServer.ACL = nil 467| 1| let encoded: Data! 468| 1| do { 469| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 470| 1| //Get dates in correct format from ParseDecoding strategy 471| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 472| 1| } catch { 473| 0| XCTFail("Should have encoded/decoded: Error: \(error)") 474| 0| return 475| 1| } 476| 200| MockURLProtocol.mockRequests { _ in 477| 200| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 478| 200| } 479| 1| 480| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 481| 100| self.saveAsync(score: score, scoreOnServer: scoreOnServer, callbackQueue: .global(qos: .background)) 482| 100| } 483| 1| } 484| | 485| 1| func testSaveAsyncMainQueue() { 486| 1| let score = GameScore(score: 10) 487| 1| 488| 1| var scoreOnServer = score 489| 1| scoreOnServer.objectId = "yarr" 490| 1| scoreOnServer.createdAt = Date() 491| 1| scoreOnServer.updatedAt = scoreOnServer.createdAt 492| 1| scoreOnServer.ACL = nil 493| 1| let encoded: Data! 494| 1| do { 495| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 496| 1| //Get dates in correct format from ParseDecoding strategy 497| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 498| 1| } catch { 499| 0| XCTFail("Should have encoded/decoded: Error: \(error)") 500| 0| return 501| 1| } 502| 2| MockURLProtocol.mockRequests { _ in 503| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 504| 2| } 505| 1| 506| 1| self.saveAsync(score: score, scoreOnServer: scoreOnServer, callbackQueue: .main) 507| 1| } 508| | 509| | // swiftlint:disable:next function_body_length 510| 101| func updateAsync(score: GameScore, scoreOnServer: GameScore, callbackQueue: DispatchQueue) { 511| 101| 512| 101| let expectation1 = XCTestExpectation(description: "Update object1") 513| 101| 514| 101| score.save(options: [], callbackQueue: callbackQueue) { result in 515| 101| 516| 101| switch result { 517| 101| 518| 101| case .success(let saved): 519| 101| guard let savedCreatedAt = saved.createdAt, 520| 101| let savedUpdatedAt = saved.updatedAt else { 521| 0| XCTFail("Should unwrap dates") 522| 0| expectation1.fulfill() 523| 0| return 524| 101| } 525| 101| guard let originalCreatedAt = score.createdAt, 526| 101| let originalUpdatedAt = score.updatedAt else { 527| 0| XCTFail("Should unwrap dates") 528| 0| expectation1.fulfill() 529| 0| return 530| 101| } 531| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 532| 101| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 533| 101| XCTAssertNil(saved.ACL) 534| 101| case .failure(let error): 535| 0| XCTFail(error.localizedDescription) 536| 101| } 537| 101| expectation1.fulfill() 538| 101| } 539| 101| 540| 101| let expectation2 = XCTestExpectation(description: "Update object2") 541| 101| score.save(options: [.useMasterKey], callbackQueue: callbackQueue) { result in 542| 101| 543| 101| switch result { 544| 101| 545| 101| case .success(let saved): 546| 101| guard let savedCreatedAt = saved.createdAt, 547| 101| let savedUpdatedAt = saved.updatedAt else { 548| 0| XCTFail("Should unwrap dates") 549| 0| expectation2.fulfill() 550| 0| return 551| 101| } 552| 101| guard let originalCreatedAt = score.createdAt, 553| 101| let originalUpdatedAt = score.updatedAt else { 554| 0| XCTFail("Should unwrap dates") 555| 0| expectation2.fulfill() 556| 0| return 557| 101| } 558| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 559| 101| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 560| 101| XCTAssertNil(saved.ACL) 561| 101| case .failure(let error): 562| 0| XCTFail(error.localizedDescription) 563| 101| } 564| 101| expectation2.fulfill() 565| 101| } 566| 101| wait(for: [expectation1, expectation2], timeout: 10.0) 567| 101| } 568| | 569| 1| func testThreadSafeUpdateAsync() { 570| 1| var score = GameScore(score: 10) 571| 1| score.objectId = "yarr" 572| 1| score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 573| 1| score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 574| 1| score.ACL = nil 575| 1| 576| 1| var scoreOnServer = score 577| 1| scoreOnServer.updatedAt = Date() 578| 1| let encoded: Data! 579| 1| do { 580| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 581| 1| //Get dates in correct format from ParseDecoding strategy 582| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 583| 1| } catch { 584| 0| XCTFail("Should have encoded/decoded: Error: \(error)") 585| 0| return 586| 1| } 587| 200| MockURLProtocol.mockRequests { _ in 588| 200| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 589| 200| } 590| 1| 591| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 592| 100| self.updateAsync(score: score, scoreOnServer: scoreOnServer, callbackQueue: .global(qos: .background)) 593| 100| } 594| 1| } 595| | 596| 1| func testUpdateAsyncMainQueue() { 597| 1| var score = GameScore(score: 10) 598| 1| score.objectId = "yarr" 599| 1| score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 600| 1| score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 601| 1| score.ACL = nil 602| 1| 603| 1| var scoreOnServer = score 604| 1| scoreOnServer.updatedAt = Date() 605| 1| let encoded: Data! 606| 1| do { 607| 1| encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) 608| 1| //Get dates in correct format from ParseDecoding strategy 609| 1| scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) 610| 1| } catch { 611| 0| XCTFail("Should have encoded/decoded: Error: \(error)") 612| 0| return 613| 1| } 614| 2| MockURLProtocol.mockRequests { _ in 615| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 616| 2| } 617| 1| self.updateAsync(score: score, scoreOnServer: scoreOnServer, callbackQueue: .main) 618| 1| } 619| |} // swiftlint:disable:this file_length /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/ParseQueryTests.swift: 1| |// 2| |// ParseQueryTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 7/26/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |import XCTest 11| |@testable import ParseSwift 12| | 13| |class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length 14| | 15| | struct GameScore: ParseObject { 16| | //: Those are required for Object 17| | var objectId: String? 18| | var createdAt: Date? 19| | var updatedAt: Date? 20| | var ACL: ParseACL? 21| | 22| | //: Your own properties 23| | var score: Int 24| | 25| | //: a custom initializer 26| 9| init(score: Int) { 27| 9| self.score = score 28| 9| } 29| | } 30| | 31| 10| override func setUp() { 32| 10| super.setUp() 33| 10| guard let url = URL(string: "http://localhost:1337/1") else { 34| 0| XCTFail("Should create valid URL") 35| 0| return 36| 10| } 37| 10| ParseSwift.initialize(applicationId: "applicationId", 38| 10| clientKey: "clientKey", 39| 10| masterKey: "masterKey", 40| 10| serverURL: url) 41| 10| } 42| | 43| 10| override func tearDown() { 44| 10| super.tearDown() 45| 10| MockURLProtocol.removeAll() 46| 10| try? ParseStorage.shared.secureStore.deleteAll() 47| 10| } 48| | 49| 1| func testConstructors() { 50| 1| let query = Query() 51| 1| XCTAssertEqual(query.className, GameScore.className) 52| 1| 53| 1| let query2 = GameScore.query() 54| 1| XCTAssertEqual(query2.className, GameScore.className) 55| 1| XCTAssertEqual(query2.className, query.className) 56| 1| 57| 1| let query3 = GameScore.query("score" > 100, "createdAt" > Date()) 58| 1| XCTAssertEqual(query3.className, GameScore.className) 59| 1| XCTAssertEqual(query3.className, query.className) 60| 1| 61| 1| let query4 = GameScore.query(["score" > 100, "createdAt" > Date()]) 62| 1| XCTAssertEqual(query4.className, GameScore.className) 63| 1| XCTAssertEqual(query4.className, query.className) 64| 1| } 65| | 66| 1| func testFind() { 67| 1| var scoreOnServer = GameScore(score: 10) 68| 1| scoreOnServer.objectId = "yarr" 69| 1| scoreOnServer.createdAt = Date() 70| 1| scoreOnServer.updatedAt = Date() 71| 1| scoreOnServer.ACL = nil 72| 1| 73| 1| let results = FindResult(results: [scoreOnServer], count: 1) 74| 1| MockURLProtocol.mockRequests { _ in 75| 1| do { 76| 1| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 77| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 78| 1| } catch { 79| 0| return nil 80| 0| } 81| 0| } 82| 1| 83| 1| let query = GameScore.query() 84| 1| do { 85| 1| 86| 1| guard let score = try query.find(options: []).first else { 87| 0| XCTFail("Should unwrap first object found") 88| 0| return 89| 1| } 90| 1| XCTAssert(score.hasSameObjectId(as: scoreOnServer)) 91| 1| } catch { 92| 0| XCTFail(error.localizedDescription) 93| 1| } 94| 1| 95| 1| } 96| | 97| 101| func findAsync(scoreOnServer: GameScore, callbackQueue: DispatchQueue) { 98| 101| let query = GameScore.query() 99| 101| let expectation = XCTestExpectation(description: "Count object1") 100| 101| query.find(options: [], callbackQueue: callbackQueue) { result in 101| 101| 102| 101| switch result { 103| 101| 104| 101| case .success(let found): 105| 101| guard let score = found.first else { 106| 0| XCTFail("Should unwrap score count") 107| 0| expectation.fulfill() 108| 0| return 109| 101| } 110| 101| XCTAssert(score.hasSameObjectId(as: scoreOnServer)) 111| 101| case .failure(let error): 112| 0| XCTFail(error.localizedDescription) 113| 101| } 114| 101| expectation.fulfill() 115| 101| } 116| 101| wait(for: [expectation], timeout: 10.0) 117| 101| } 118| | 119| 1| func testThreadSafeFindAsync() { 120| 1| var scoreOnServer = GameScore(score: 10) 121| 1| scoreOnServer.objectId = "yarr" 122| 1| scoreOnServer.createdAt = Date() 123| 1| scoreOnServer.updatedAt = Date() 124| 1| scoreOnServer.ACL = nil 125| 1| 126| 1| let results = FindResult(results: [scoreOnServer], count: 1) 127| 100| MockURLProtocol.mockRequests { _ in 128| 100| do { 129| 100| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 130| 100| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 131| 100| } catch { 132| 0| return nil 133| 0| } 134| 0| } 135| 1| 136| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 137| 100| findAsync(scoreOnServer: scoreOnServer, callbackQueue: .global(qos: .background)) 138| 100| } 139| 1| } 140| | 141| 1| func testFindAsyncMainQueue() { 142| 1| var scoreOnServer = GameScore(score: 10) 143| 1| scoreOnServer.objectId = "yarr" 144| 1| scoreOnServer.createdAt = Date() 145| 1| scoreOnServer.updatedAt = Date() 146| 1| scoreOnServer.ACL = nil 147| 1| 148| 1| let results = FindResult(results: [scoreOnServer], count: 1) 149| 1| MockURLProtocol.mockRequests { _ in 150| 1| do { 151| 1| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 152| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 153| 1| } catch { 154| 0| return nil 155| 0| } 156| 0| } 157| 1| findAsync(scoreOnServer: scoreOnServer, callbackQueue: .main) 158| 1| } 159| | 160| 1| func testFirst() { 161| 1| var scoreOnServer = GameScore(score: 10) 162| 1| scoreOnServer.objectId = "yarr" 163| 1| scoreOnServer.createdAt = Date() 164| 1| scoreOnServer.updatedAt = Date() 165| 1| scoreOnServer.ACL = nil 166| 1| 167| 1| let results = FindResult(results: [scoreOnServer], count: 1) 168| 1| MockURLProtocol.mockRequests { _ in 169| 1| do { 170| 1| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 171| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 172| 1| } catch { 173| 0| return nil 174| 0| } 175| 0| } 176| 1| 177| 1| let query = GameScore.query() 178| 1| do { 179| 1| 180| 1| guard let score = try query.first(options: []) else { 181| 0| XCTFail("Should unwrap first object found") 182| 0| return 183| 1| } 184| 1| XCTAssert(score.hasSameObjectId(as: scoreOnServer)) 185| 1| } catch { 186| 0| XCTFail(error.localizedDescription) 187| 1| } 188| 1| 189| 1| } 190| | 191| 101| func firstAsync(scoreOnServer: GameScore, callbackQueue: DispatchQueue) { 192| 101| let query = GameScore.query() 193| 101| let expectation = XCTestExpectation(description: "Count object1") 194| 101| query.first(options: [], callbackQueue: callbackQueue) { result in 195| 101| 196| 101| switch result { 197| 101| 198| 101| case .success(let score): 199| 101| XCTAssert(score.hasSameObjectId(as: scoreOnServer)) 200| 101| 201| 101| case .failure(let error): 202| 0| XCTFail(error.localizedDescription) 203| 101| } 204| 101| expectation.fulfill() 205| 101| } 206| 101| wait(for: [expectation], timeout: 10.0) 207| 101| } 208| | 209| 1| func testThreadSafeFirstAsync() { 210| 1| var scoreOnServer = GameScore(score: 10) 211| 1| scoreOnServer.objectId = "yarr" 212| 1| scoreOnServer.createdAt = Date() 213| 1| scoreOnServer.updatedAt = Date() 214| 1| scoreOnServer.ACL = nil 215| 1| 216| 1| let results = FindResult(results: [scoreOnServer], count: 1) 217| 100| MockURLProtocol.mockRequests { _ in 218| 100| do { 219| 100| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 220| 100| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 221| 100| } catch { 222| 0| return nil 223| 0| } 224| 0| } 225| 1| 226| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 227| 100| firstAsync(scoreOnServer: scoreOnServer, callbackQueue: .global(qos: .background)) 228| 100| } 229| 1| } 230| | 231| 1| func testFirstAsyncMainQueue() { 232| 1| var scoreOnServer = GameScore(score: 10) 233| 1| scoreOnServer.objectId = "yarr" 234| 1| scoreOnServer.createdAt = Date() 235| 1| scoreOnServer.updatedAt = Date() 236| 1| scoreOnServer.ACL = nil 237| 1| 238| 1| let results = FindResult(results: [scoreOnServer], count: 1) 239| 1| MockURLProtocol.mockRequests { _ in 240| 1| do { 241| 1| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 242| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 243| 1| } catch { 244| 0| return nil 245| 0| } 246| 0| } 247| 1| firstAsync(scoreOnServer: scoreOnServer, callbackQueue: .main) 248| 1| } 249| | 250| 1| func testCount() { 251| 1| var scoreOnServer = GameScore(score: 10) 252| 1| scoreOnServer.objectId = "yarr" 253| 1| scoreOnServer.createdAt = Date() 254| 1| scoreOnServer.updatedAt = Date() 255| 1| scoreOnServer.ACL = nil 256| 1| 257| 1| let results = FindResult(results: [scoreOnServer], count: 1) 258| 1| MockURLProtocol.mockRequests { _ in 259| 1| do { 260| 1| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 261| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 262| 1| } catch { 263| 0| return nil 264| 0| } 265| 0| } 266| 1| 267| 1| let query = GameScore.query() 268| 1| do { 269| 1| 270| 1| let scoreCount = try query.count(options: []) 271| 1| XCTAssertEqual(scoreCount, 1) 272| 1| } catch { 273| 0| XCTFail(error.localizedDescription) 274| 1| } 275| 1| 276| 1| } 277| | 278| 101| func countAsync(scoreOnServer: GameScore, callbackQueue: DispatchQueue) { 279| 101| let query = GameScore.query() 280| 101| let expectation = XCTestExpectation(description: "Count object1") 281| 101| query.count(options: [], callbackQueue: callbackQueue) { result in 282| 101| 283| 101| switch result { 284| 101| 285| 101| case .success(let scoreCount): 286| 101| XCTAssertEqual(scoreCount, 1) 287| 101| case .failure(let error): 288| 0| XCTFail(error.localizedDescription) 289| 101| } 290| 101| expectation.fulfill() 291| 101| } 292| 101| wait(for: [expectation], timeout: 10.0) 293| 101| } 294| | 295| 1| func testThreadSafeCountAsync() { 296| 1| var scoreOnServer = GameScore(score: 10) 297| 1| scoreOnServer.objectId = "yarr" 298| 1| scoreOnServer.createdAt = Date() 299| 1| scoreOnServer.updatedAt = Date() 300| 1| scoreOnServer.ACL = nil 301| 1| 302| 1| let results = FindResult(results: [scoreOnServer], count: 1) 303| 100| MockURLProtocol.mockRequests { _ in 304| 100| do { 305| 100| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 306| 100| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 307| 100| } catch { 308| 0| return nil 309| 0| } 310| 0| } 311| 1| 312| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 313| 100| countAsync(scoreOnServer: scoreOnServer, callbackQueue: .global(qos: .background)) 314| 100| } 315| 1| } 316| | 317| 1| func testCountAsyncMainQueue() { 318| 1| var scoreOnServer = GameScore(score: 10) 319| 1| scoreOnServer.objectId = "yarr" 320| 1| scoreOnServer.createdAt = Date() 321| 1| scoreOnServer.updatedAt = Date() 322| 1| scoreOnServer.ACL = nil 323| 1| 324| 1| let results = FindResult(results: [scoreOnServer], count: 1) 325| 1| MockURLProtocol.mockRequests { _ in 326| 1| do { 327| 1| let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) 328| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 329| 1| } catch { 330| 0| return nil 331| 0| } 332| 0| } 333| 1| countAsync(scoreOnServer: scoreOnServer, callbackQueue: .main) 334| 1| } 335| |} /Users/runner/work/Parse-Swift/Parse-Swift/Tests/ParseSwiftTests/ParseUserCommandTests.swift: 1| |// 2| |// ParseUserCommandTests.swift 3| |// ParseSwiftTests 4| |// 5| |// Created by Corey Baker on 7/21/20. 6| |// Copyright © 2020 Parse Community. All rights reserved. 7| |// 8| | 9| |import Foundation 10| |import XCTest 11| |@testable import ParseSwift 12| | 13| |class ParseUserCommandTests: XCTestCase { // swiftlint:disable:this type_body_length 14| | 15| | struct User: ParseUser { 16| | //: Those are required for Object 17| | var objectId: String? 18| | var createdAt: Date? 19| | var updatedAt: Date? 20| | var ACL: ParseACL? 21| | 22| | // provided by User 23| | var username: String? 24| | var email: String? 25| | var password: String? 26| | 27| | // Your custom keys 28| | var customKey: String? 29| | } 30| | 31| | struct LoginSignupResponse: ParseUser { 32| | var objectId: String? 33| | var createdAt: Date? 34| | var sessionToken: String 35| | var updatedAt: Date? 36| | var ACL: ParseACL? 37| | 38| | // provided by User 39| | var username: String? 40| | var email: String? 41| | var password: String? 42| | 43| | // Your custom keys 44| | var customKey: String? 45| | 46| 11| init() { 47| 11| self.createdAt = Date() 48| 11| self.updatedAt = Date() 49| 11| self.objectId = "yarr" 50| 11| self.ACL = nil 51| 11| self.customKey = "blah" 52| 11| self.sessionToken = "myToken" 53| 11| self.username = "hello10" 54| 11| self.password = "world" 55| 11| self.email = "hello@parse.com" 56| 11| } 57| | } 58| | 59| 19| override func setUp() { 60| 19| super.setUp() 61| 19| guard let url = URL(string: "http://localhost:1337/1") else { 62| 0| XCTFail("Should create valid URL") 63| 0| return 64| 19| } 65| 19| ParseSwift.initialize(applicationId: "applicationId", 66| 19| clientKey: "clientKey", 67| 19| masterKey: "masterKey", 68| 19| serverURL: url) 69| 19| } 70| | 71| 19| override func tearDown() { 72| 19| super.tearDown() 73| 19| MockURLProtocol.removeAll() 74| 19| try? ParseStorage.shared.secureStore.deleteAll() 75| 19| } 76| | 77| 1| func testFetchCommand() { 78| 1| var user = User() 79| 1| let className = user.className 80| 1| let objectId = "yarr" 81| 1| user.objectId = objectId 82| 1| do { 83| 1| let command = try user.fetchCommand() 84| 1| XCTAssertNotNil(command) 85| 1| XCTAssertEqual(command.path.urlComponent, "/classes/\(className)/\(objectId)") 86| 1| XCTAssertEqual(command.method, API.Method.GET) 87| 1| XCTAssertNil(command.params) 88| 1| XCTAssertNil(command.body) 89| 1| XCTAssertNil(command.data) 90| 1| } catch { 91| 0| XCTFail(error.localizedDescription) 92| 1| } 93| 1| } 94| | 95| 1| func testFetch() { // swiftlint:disable:this function_body_length 96| 1| var user = User() 97| 1| let objectId = "yarr" 98| 1| user.objectId = objectId 99| 1| 100| 1| var userOnServer = user 101| 1| userOnServer.createdAt = Date() 102| 1| userOnServer.updatedAt = Date() 103| 1| userOnServer.ACL = nil 104| 1| let encoded: Data! 105| 1| do { 106| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 107| 1| //Get dates in correct format from ParseDecoding strategy 108| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 109| 1| } catch { 110| 0| XCTFail("Should encode/decode. Error \(error)") 111| 0| return 112| 1| } 113| 2| MockURLProtocol.mockRequests { _ in 114| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 115| 2| } 116| 1| 117| 1| do { 118| 1| let fetched = try user.fetch() 119| 1| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 120| 1| guard let fetchedCreatedAt = fetched.createdAt, 121| 1| let fetchedUpdatedAt = fetched.updatedAt else { 122| 0| XCTFail("Should unwrap dates") 123| 0| return 124| 1| } 125| 1| guard let originalCreatedAt = userOnServer.createdAt, 126| 1| let originalUpdatedAt = userOnServer.updatedAt else { 127| 0| XCTFail("Should unwrap dates") 128| 0| return 129| 1| } 130| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 131| 1| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 132| 1| XCTAssertNil(fetched.ACL) 133| 1| } catch { 134| 0| XCTFail(error.localizedDescription) 135| 1| } 136| 1| 137| 1| do { 138| 1| let fetched = try user.fetch(options: [.useMasterKey]) 139| 1| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 140| 1| guard let fetchedCreatedAt = fetched.createdAt, 141| 1| let fetchedUpdatedAt = fetched.updatedAt else { 142| 0| XCTFail("Should unwrap dates") 143| 0| return 144| 1| } 145| 1| guard let originalCreatedAt = userOnServer.createdAt, 146| 1| let originalUpdatedAt = userOnServer.updatedAt else { 147| 0| XCTFail("Should unwrap dates") 148| 0| return 149| 1| } 150| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 151| 1| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 152| 1| XCTAssertNil(fetched.ACL) 153| 1| } catch { 154| 0| XCTFail(error.localizedDescription) 155| 1| } 156| 1| } 157| | 158| 1| func testFetchAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length 159| 1| XCTAssertNil(User.current?.objectId) 160| 1| testUserLogin() 161| 1| MockURLProtocol.removeAll() 162| 1| XCTAssertNotNil(User.current?.objectId) 163| 1| 164| 1| guard let user = User.current else { 165| 0| XCTFail("Should unwrap") 166| 0| return 167| 1| } 168| 1| 169| 1| var userOnServer = user 170| 1| userOnServer.createdAt = User.current?.createdAt 171| 1| userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) 172| 1| 173| 1| let encoded: Data! 174| 1| do { 175| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 176| 1| //Get dates in correct format from ParseDecoding strategy 177| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 178| 1| } catch { 179| 0| XCTFail("Should encode/decode. Error \(error)") 180| 0| return 181| 1| } 182| 1| MockURLProtocol.mockRequests { _ in 183| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 184| 1| } 185| 1| 186| 1| do { 187| 1| let fetched = try user.fetch(options: [.useMasterKey]) 188| 1| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 189| 1| guard let fetchedCreatedAt = fetched.createdAt, 190| 1| let fetchedUpdatedAt = fetched.updatedAt else { 191| 0| XCTFail("Should unwrap dates") 192| 0| return 193| 1| } 194| 1| guard let originalCreatedAt = user.createdAt, 195| 1| let originalUpdatedAt = user.updatedAt else { 196| 0| XCTFail("Should unwrap dates") 197| 0| return 198| 1| } 199| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 200| 1| XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt) 201| 1| XCTAssertNil(fetched.ACL) 202| 1| 203| 1| //Should be updated in memory 204| 1| XCTAssertEqual(User.current?.updatedAt, fetchedUpdatedAt) 205| 1| 206| 1| //Shold be updated in Keychain 207| 1| guard let keychainUser: CurrentUserContainer 208| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else { 209| 0| XCTFail("Should get object from Keychain") 210| 0| return 211| 1| } 212| 1| XCTAssertEqual(keychainUser.currentUser?.updatedAt, fetchedUpdatedAt) 213| 1| 214| 1| } catch { 215| 0| XCTFail(error.localizedDescription) 216| 1| } 217| 1| } 218| | 219| 1| func testFetchAsyncAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length 220| 1| XCTAssertNil(User.current?.objectId) 221| 1| testUserLogin() 222| 1| MockURLProtocol.removeAll() 223| 1| XCTAssertNotNil(User.current?.objectId) 224| 1| 225| 1| guard let user = User.current else { 226| 0| XCTFail("Should unwrap") 227| 0| return 228| 1| } 229| 1| 230| 1| var userOnServer = user 231| 1| userOnServer.createdAt = User.current?.createdAt 232| 1| userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) 233| 1| 234| 1| let encoded: Data! 235| 1| do { 236| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 237| 1| //Get dates in correct format from ParseDecoding strategy 238| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 239| 1| } catch { 240| 0| XCTFail("Should encode/decode. Error \(error)") 241| 0| return 242| 1| } 243| 1| MockURLProtocol.mockRequests { _ in 244| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 245| 1| } 246| 1| 247| 1| let expectation1 = XCTestExpectation(description: "Fetch user1") 248| 1| user.fetch(options: [], callbackQueue: .global(qos: .background)) { result in 249| 1| 250| 1| switch result { 251| 1| case .success(let fetched): 252| 1| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 253| 1| guard let fetchedCreatedAt = fetched.createdAt, 254| 1| let fetchedUpdatedAt = fetched.updatedAt else { 255| 0| XCTFail("Should unwrap dates") 256| 0| return 257| 1| } 258| 1| guard let originalCreatedAt = user.createdAt, 259| 1| let originalUpdatedAt = user.updatedAt else { 260| 0| XCTFail("Should unwrap dates") 261| 0| return 262| 1| } 263| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 264| 1| XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt) 265| 1| XCTAssertNil(fetched.ACL) 266| 1| 267| 1| //Should be updated in memory 268| 1| XCTAssertEqual(User.current?.updatedAt, fetchedUpdatedAt) 269| 1| 270| 1| //Shold be updated in Keychain 271| 1| guard let keychainUser: CurrentUserContainer 272| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else { 273| 0| XCTFail("Should get object from Keychain") 274| 0| return 275| 1| } 276| 1| XCTAssertEqual(keychainUser.currentUser?.updatedAt, fetchedUpdatedAt) 277| 1| case .failure(let error): 278| 0| XCTFail(error.localizedDescription) 279| 1| } 280| 1| expectation1.fulfill() 281| 1| } 282| 1| wait(for: [expectation1], timeout: 10.0) 283| 1| } 284| | 285| | // swiftlint:disable:next function_body_length 286| 100| func fetchAsync(user: User, userOnServer: User) { 287| 100| 288| 100| let expectation1 = XCTestExpectation(description: "Fetch user1") 289| 100| user.fetch(options: [], callbackQueue: .global(qos: .background)) { result in 290| 100| 291| 100| switch result { 292| 100| 293| 100| case .success(let fetched): 294| 100| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 295| 100| guard let fetchedCreatedAt = fetched.createdAt, 296| 100| let fetchedUpdatedAt = fetched.updatedAt else { 297| 0| XCTFail("Should unwrap dates") 298| 0| expectation1.fulfill() 299| 0| return 300| 100| } 301| 100| guard let originalCreatedAt = userOnServer.createdAt, 302| 100| let originalUpdatedAt = userOnServer.updatedAt else { 303| 0| XCTFail("Should unwrap dates") 304| 0| expectation1.fulfill() 305| 0| return 306| 100| } 307| 100| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 308| 100| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 309| 100| XCTAssertNil(fetched.ACL) 310| 100| case .failure(let error): 311| 0| XCTFail(error.localizedDescription) 312| 100| } 313| 100| expectation1.fulfill() 314| 100| } 315| 100| 316| 100| let expectation2 = XCTestExpectation(description: "Fetch user2") 317| 100| user.fetch(options: [.sessionToken("")], callbackQueue: .global(qos: .background)) { result in 318| 100| 319| 100| switch result { 320| 100| 321| 100| case .success(let fetched): 322| 100| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 323| 100| guard let fetchedCreatedAt = fetched.createdAt, 324| 100| let fetchedUpdatedAt = fetched.updatedAt else { 325| 0| XCTFail("Should unwrap dates") 326| 0| expectation2.fulfill() 327| 0| return 328| 100| } 329| 100| guard let originalCreatedAt = userOnServer.createdAt, 330| 100| let originalUpdatedAt = userOnServer.updatedAt else { 331| 0| XCTFail("Should unwrap dates") 332| 0| expectation2.fulfill() 333| 0| return 334| 100| } 335| 100| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 336| 100| XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt) 337| 100| XCTAssertNil(fetched.ACL) 338| 100| case .failure(let error): 339| 0| XCTFail(error.localizedDescription) 340| 100| } 341| 100| expectation2.fulfill() 342| 100| } 343| 100| wait(for: [expectation1, expectation2], timeout: 10.0) 344| 100| } 345| | 346| 1| func testThreadSafeFetchAsync() { 347| 1| var user = User() 348| 1| let objectId = "yarr" 349| 1| user.objectId = objectId 350| 1| 351| 1| var userOnServer = user 352| 1| userOnServer.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 353| 1| userOnServer.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 354| 1| userOnServer.ACL = nil 355| 1| let encoded: Data! 356| 1| do { 357| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 358| 1| //Get dates in correct format from ParseDecoding strategy 359| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 360| 1| } catch { 361| 0| XCTFail("Should encode/decode. Error \(error)") 362| 0| return 363| 1| } 364| 200| MockURLProtocol.mockRequests { _ in 365| 200| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 366| 200| } 367| 1| 368| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 369| 100| self.fetchAsync(user: user, userOnServer: userOnServer) 370| 100| } 371| 1| } 372| | 373| 1| func testSaveCommand() { 374| 1| let user = User() 375| 1| let className = user.className 376| 1| 377| 1| let command = user.saveCommand() 378| 1| XCTAssertNotNil(command) 379| 1| XCTAssertEqual(command.path.urlComponent, "/classes/\(className)") 380| 1| XCTAssertEqual(command.method, API.Method.POST) 381| 1| XCTAssertNil(command.params) 382| 1| XCTAssertNotNil(command.body) 383| 1| XCTAssertNotNil(command.data) 384| 1| } 385| | 386| 1| func testUpdateCommand() { 387| 1| var user = User() 388| 1| let className = user.className 389| 1| let objectId = "yarr" 390| 1| user.objectId = objectId 391| 1| 392| 1| let command = user.saveCommand() 393| 1| XCTAssertNotNil(command) 394| 1| XCTAssertEqual(command.path.urlComponent, "/classes/\(className)/\(objectId)") 395| 1| XCTAssertEqual(command.method, API.Method.PUT) 396| 1| XCTAssertNil(command.params) 397| 1| XCTAssertNotNil(command.body) 398| 1| XCTAssertNotNil(command.data) 399| 1| } 400| | 401| 1| func testSaveAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length 402| 1| XCTAssertNil(User.current?.objectId) 403| 1| testUserLogin() 404| 1| MockURLProtocol.removeAll() 405| 1| XCTAssertNotNil(User.current?.objectId) 406| 1| 407| 1| guard let user = User.current else { 408| 0| XCTFail("Should unwrap") 409| 0| return 410| 1| } 411| 1| 412| 1| var userOnServer = user 413| 1| userOnServer.createdAt = User.current?.createdAt 414| 1| userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) 415| 1| 416| 1| let encoded: Data! 417| 1| do { 418| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 419| 1| //Get dates in correct format from ParseDecoding strategy 420| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 421| 1| } catch { 422| 0| XCTFail("Should encode/decode. Error \(error)") 423| 0| return 424| 1| } 425| 1| MockURLProtocol.mockRequests { _ in 426| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 427| 1| } 428| 1| 429| 1| do { 430| 1| let fetched = try user.save(options: [.useMasterKey]) 431| 1| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 432| 1| guard let fetchedCreatedAt = fetched.createdAt, 433| 1| let fetchedUpdatedAt = fetched.updatedAt else { 434| 0| XCTFail("Should unwrap dates") 435| 0| return 436| 1| } 437| 1| guard let originalCreatedAt = user.createdAt, 438| 1| let originalUpdatedAt = user.updatedAt else { 439| 0| XCTFail("Should unwrap dates") 440| 0| return 441| 1| } 442| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 443| 1| XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt) 444| 1| XCTAssertNil(fetched.ACL) 445| 1| 446| 1| //Should be updated in memory 447| 1| XCTAssertEqual(User.current?.updatedAt, fetchedUpdatedAt) 448| 1| 449| 1| //Shold be updated in Keychain 450| 1| guard let keychainUser: CurrentUserContainer 451| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else { 452| 0| XCTFail("Should get object from Keychain") 453| 0| return 454| 1| } 455| 1| XCTAssertEqual(keychainUser.currentUser?.updatedAt, fetchedUpdatedAt) 456| 1| 457| 1| } catch { 458| 0| XCTFail(error.localizedDescription) 459| 1| } 460| 1| } 461| | 462| 1| func testSaveAsyncAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length 463| 1| XCTAssertNil(User.current?.objectId) 464| 1| testUserLogin() 465| 1| MockURLProtocol.removeAll() 466| 1| XCTAssertNotNil(User.current?.objectId) 467| 1| 468| 1| guard let user = User.current else { 469| 0| XCTFail("Should unwrap") 470| 0| return 471| 1| } 472| 1| 473| 1| var userOnServer = user 474| 1| userOnServer.createdAt = User.current?.createdAt 475| 1| userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300) 476| 1| 477| 1| let encoded: Data! 478| 1| do { 479| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 480| 1| //Get dates in correct format from ParseDecoding strategy 481| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 482| 1| } catch { 483| 0| XCTFail("Should encode/decode. Error \(error)") 484| 0| return 485| 1| } 486| 1| MockURLProtocol.mockRequests { _ in 487| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 488| 1| } 489| 1| 490| 1| let expectation1 = XCTestExpectation(description: "Fetch user1") 491| 1| user.save(options: [], callbackQueue: .global(qos: .background)) { result in 492| 1| 493| 1| switch result { 494| 1| case .success(let fetched): 495| 1| XCTAssert(fetched.hasSameObjectId(as: userOnServer)) 496| 1| guard let fetchedCreatedAt = fetched.createdAt, 497| 1| let fetchedUpdatedAt = fetched.updatedAt else { 498| 0| XCTFail("Should unwrap dates") 499| 0| return 500| 1| } 501| 1| guard let originalCreatedAt = user.createdAt, 502| 1| let originalUpdatedAt = user.updatedAt else { 503| 0| XCTFail("Should unwrap dates") 504| 0| return 505| 1| } 506| 1| XCTAssertEqual(fetchedCreatedAt, originalCreatedAt) 507| 1| XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt) 508| 1| XCTAssertNil(fetched.ACL) 509| 1| 510| 1| //Should be updated in memory 511| 1| XCTAssertEqual(User.current?.updatedAt, fetchedUpdatedAt) 512| 1| 513| 1| //Shold be updated in Keychain 514| 1| guard let keychainUser: CurrentUserContainer 515| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else { 516| 0| XCTFail("Should get object from Keychain") 517| 0| return 518| 1| } 519| 1| XCTAssertEqual(keychainUser.currentUser?.updatedAt, fetchedUpdatedAt) 520| 1| case .failure(let error): 521| 0| XCTFail(error.localizedDescription) 522| 1| } 523| 1| expectation1.fulfill() 524| 1| } 525| 1| wait(for: [expectation1], timeout: 10.0) 526| 1| } 527| | 528| 1| func testUpdate() { // swiftlint:disable:this function_body_length 529| 1| var user = User() 530| 1| let objectId = "yarr" 531| 1| user.objectId = objectId 532| 1| user.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 533| 1| user.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 534| 1| user.ACL = nil 535| 1| 536| 1| var userOnServer = user 537| 1| userOnServer.updatedAt = Date() 538| 1| 539| 1| let encoded: Data! 540| 1| do { 541| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 542| 1| //Get dates in correct format from ParseDecoding strategy 543| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 544| 1| } catch { 545| 0| XCTFail("Should encode/decode. Error \(error)") 546| 0| return 547| 1| } 548| 2| MockURLProtocol.mockRequests { _ in 549| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 550| 2| } 551| 1| do { 552| 1| let saved = try user.save() 553| 1| guard let savedCreatedAt = saved.createdAt, 554| 1| let savedUpdatedAt = saved.updatedAt else { 555| 0| XCTFail("Should unwrap dates") 556| 0| return 557| 1| } 558| 1| guard let originalCreatedAt = user.createdAt, 559| 1| let originalUpdatedAt = user.updatedAt else { 560| 0| XCTFail("Should unwrap dates") 561| 0| return 562| 1| } 563| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 564| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 565| 1| XCTAssertNil(saved.ACL) 566| 1| } catch { 567| 0| XCTFail(error.localizedDescription) 568| 1| } 569| 1| 570| 1| do { 571| 1| let saved = try user.save(options: [.useMasterKey]) 572| 1| guard let savedCreatedAt = saved.createdAt, 573| 1| let savedUpdatedAt = saved.updatedAt else { 574| 0| XCTFail("Should unwrap dates") 575| 0| return 576| 1| } 577| 1| guard let originalCreatedAt = user.createdAt, 578| 1| let originalUpdatedAt = user.updatedAt else { 579| 0| XCTFail("Should unwrap dates") 580| 0| return 581| 1| } 582| 1| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 583| 1| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 584| 1| XCTAssertNil(saved.ACL) 585| 1| } catch { 586| 0| XCTFail(error.localizedDescription) 587| 1| } 588| 1| } 589| | 590| | // swiftlint:disable:next function_body_length 591| 101| func updateAsync(user: User, userOnServer: User, callbackQueue: DispatchQueue) { 592| 101| 593| 101| let expectation1 = XCTestExpectation(description: "Update user1") 594| 101| user.save(options: [], callbackQueue: callbackQueue) { result in 595| 101| 596| 101| switch result { 597| 101| 598| 101| case .success(let saved): 599| 101| guard let savedCreatedAt = saved.createdAt, 600| 101| let savedUpdatedAt = saved.updatedAt else { 601| 0| XCTFail("Should unwrap dates") 602| 0| expectation1.fulfill() 603| 0| return 604| 101| } 605| 101| guard let originalCreatedAt = user.createdAt, 606| 101| let originalUpdatedAt = user.updatedAt else { 607| 0| XCTFail("Should unwrap dates") 608| 0| expectation1.fulfill() 609| 0| return 610| 101| } 611| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 612| 101| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 613| 101| XCTAssertNil(saved.ACL) 614| 101| case .failure(let error): 615| 0| XCTFail(error.localizedDescription) 616| 101| } 617| 101| expectation1.fulfill() 618| 101| } 619| 101| 620| 101| let expectation2 = XCTestExpectation(description: "Update user2") 621| 101| user.save(options: [.useMasterKey], callbackQueue: callbackQueue) { result in 622| 101| 623| 101| switch result { 624| 101| 625| 101| case .success(let saved): 626| 101| guard let savedCreatedAt = saved.createdAt, 627| 101| let savedUpdatedAt = saved.updatedAt else { 628| 0| XCTFail("Should unwrap dates") 629| 0| expectation2.fulfill() 630| 0| return 631| 101| } 632| 101| guard let originalCreatedAt = user.createdAt, 633| 101| let originalUpdatedAt = user.updatedAt else { 634| 0| XCTFail("Should unwrap dates") 635| 0| expectation2.fulfill() 636| 0| return 637| 101| } 638| 101| XCTAssertEqual(savedCreatedAt, originalCreatedAt) 639| 101| XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) 640| 101| XCTAssertNil(saved.ACL) 641| 101| case .failure(let error): 642| 0| XCTFail(error.localizedDescription) 643| 101| } 644| 101| expectation2.fulfill() 645| 101| } 646| 101| wait(for: [expectation1, expectation2], timeout: 10.0) 647| 101| } 648| | 649| 1| func testThreadSafeUpdateAsync() { 650| 1| var user = User() 651| 1| let objectId = "yarr" 652| 1| user.objectId = objectId 653| 1| user.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 654| 1| user.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 655| 1| user.ACL = nil 656| 1| 657| 1| var userOnServer = user 658| 1| userOnServer.updatedAt = Date() 659| 1| let encoded: Data! 660| 1| do { 661| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 662| 1| //Get dates in correct format from ParseDecoding strategy 663| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 664| 1| } catch { 665| 0| XCTFail("Should encode/decode. Error \(error)") 666| 0| return 667| 1| } 668| 200| MockURLProtocol.mockRequests { _ in 669| 200| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 670| 200| } 671| 1| 672| 100| DispatchQueue.concurrentPerform(iterations: 100) {_ in 673| 100| self.updateAsync(user: user, userOnServer: userOnServer, callbackQueue: .global(qos: .background)) 674| 100| } 675| 1| } 676| | 677| 1| func testUpdateAsyncMainQueue() { 678| 1| var user = User() 679| 1| let objectId = "yarr" 680| 1| user.objectId = objectId 681| 1| user.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 682| 1| user.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) 683| 1| user.ACL = nil 684| 1| 685| 1| var userOnServer = user 686| 1| userOnServer.updatedAt = Date() 687| 1| let encoded: Data! 688| 1| do { 689| 1| encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) 690| 1| //Get dates in correct format from ParseDecoding strategy 691| 1| userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) 692| 1| } catch { 693| 0| XCTFail("Should encode/decode. Error \(error)") 694| 0| return 695| 1| } 696| 2| MockURLProtocol.mockRequests { _ in 697| 2| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 698| 2| } 699| 1| 700| 1| self.updateAsync(user: user, userOnServer: userOnServer, callbackQueue: .main) 701| 1| } 702| | 703| 1| func testUserSignUp() { 704| 1| let loginResponse = LoginSignupResponse() 705| 1| 706| 1| MockURLProtocol.mockRequests { _ in 707| 1| do { 708| 1| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 709| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 710| 1| } catch { 711| 0| return nil 712| 0| } 713| 0| } 714| 1| do { 715| 1| let signedUp = try User.signup(username: loginResponse.username!, password: loginResponse.password!) 716| 1| XCTAssertNotNil(signedUp) 717| 1| XCTAssertNotNil(signedUp.createdAt) 718| 1| XCTAssertNotNil(signedUp.updatedAt) 719| 1| XCTAssertNotNil(signedUp.email) 720| 1| XCTAssertNotNil(signedUp.username) 721| 1| XCTAssertNotNil(signedUp.password) 722| 1| XCTAssertNotNil(signedUp.objectId) 723| 1| XCTAssertNotNil(signedUp.sessionToken) 724| 1| XCTAssertNotNil(signedUp.customKey) 725| 1| XCTAssertNil(signedUp.ACL) 726| 1| 727| 1| guard let userFromKeychain = BaseParseUser.current else { 728| 0| XCTFail("Couldn't get CurrentUser from Keychain") 729| 0| return 730| 1| } 731| 1| 732| 1| XCTAssertNotNil(userFromKeychain.createdAt) 733| 1| XCTAssertNotNil(userFromKeychain.updatedAt) 734| 1| XCTAssertNotNil(userFromKeychain.email) 735| 1| XCTAssertNotNil(userFromKeychain.username) 736| 1| XCTAssertNotNil(userFromKeychain.password) 737| 1| XCTAssertNotNil(userFromKeychain.objectId) 738| 1| XCTAssertNotNil(userFromKeychain.sessionToken) 739| 1| XCTAssertNil(userFromKeychain.ACL) 740| 1| 741| 1| } catch { 742| 0| XCTFail(error.localizedDescription) 743| 1| } 744| 1| } 745| | 746| 1| func signUpAsync(loginResponse: LoginSignupResponse, callbackQueue: DispatchQueue) { 747| 1| 748| 1| let expectation1 = XCTestExpectation(description: "Signup user1") 749| 1| User.signup(username: loginResponse.username!, password: loginResponse.password!, 750| 1| callbackQueue: callbackQueue) { result in 751| 1| switch result { 752| 1| 753| 1| case .success(let signedUp): 754| 1| XCTAssertNotNil(signedUp.createdAt) 755| 1| XCTAssertNotNil(signedUp.updatedAt) 756| 1| XCTAssertNotNil(signedUp.email) 757| 1| XCTAssertNotNil(signedUp.username) 758| 1| XCTAssertNotNil(signedUp.password) 759| 1| XCTAssertNotNil(signedUp.objectId) 760| 1| XCTAssertNotNil(signedUp.sessionToken) 761| 1| XCTAssertNotNil(signedUp.customKey) 762| 1| XCTAssertNil(signedUp.ACL) 763| 1| 764| 1| guard let userFromKeychain = BaseParseUser.current else { 765| 0| XCTFail("Couldn't get CurrentUser from Keychain") 766| 0| return 767| 1| } 768| 1| 769| 1| XCTAssertNotNil(userFromKeychain.createdAt) 770| 1| XCTAssertNotNil(userFromKeychain.updatedAt) 771| 1| XCTAssertNotNil(userFromKeychain.email) 772| 1| XCTAssertNotNil(userFromKeychain.username) 773| 1| XCTAssertNotNil(userFromKeychain.password) 774| 1| XCTAssertNotNil(userFromKeychain.objectId) 775| 1| XCTAssertNotNil(userFromKeychain.sessionToken) 776| 1| XCTAssertNil(userFromKeychain.ACL) 777| 1| case .failure(let error): 778| 0| XCTFail(error.localizedDescription) 779| 1| } 780| 1| expectation1.fulfill() 781| 1| } 782| 1| wait(for: [expectation1], timeout: 10.0) 783| 1| } 784| | 785| 1| func testSignUpAsyncMainQueue() { 786| 1| let loginResponse = LoginSignupResponse() 787| 1| 788| 1| MockURLProtocol.mockRequests { _ in 789| 1| do { 790| 1| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 791| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 792| 1| } catch { 793| 0| return nil 794| 0| } 795| 0| } 796| 1| 797| 1| self.signUpAsync(loginResponse: loginResponse, callbackQueue: .main) 798| 1| } 799| | 800| 6| func testUserLogin() { 801| 6| let loginResponse = LoginSignupResponse() 802| 6| 803| 6| MockURLProtocol.mockRequests { _ in 804| 6| do { 805| 6| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 806| 6| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 807| 6| } catch { 808| 0| return nil 809| 0| } 810| 0| } 811| 6| do { 812| 6| let loggedIn = try User.login(username: loginResponse.username!, password: loginResponse.password!) 813| 6| XCTAssertNotNil(loggedIn) 814| 6| XCTAssertNotNil(loggedIn.createdAt) 815| 6| XCTAssertNotNil(loggedIn.updatedAt) 816| 6| XCTAssertNotNil(loggedIn.email) 817| 6| XCTAssertNotNil(loggedIn.username) 818| 6| XCTAssertNotNil(loggedIn.password) 819| 6| XCTAssertNotNil(loggedIn.objectId) 820| 6| XCTAssertNotNil(loggedIn.sessionToken) 821| 6| XCTAssertNotNil(loggedIn.customKey) 822| 6| XCTAssertNil(loggedIn.ACL) 823| 6| 824| 6| guard let userFromKeychain = BaseParseUser.current else { 825| 0| XCTFail("Couldn't get CurrentUser from Keychain") 826| 0| return 827| 6| } 828| 6| 829| 6| XCTAssertNotNil(userFromKeychain.createdAt) 830| 6| XCTAssertNotNil(userFromKeychain.updatedAt) 831| 6| XCTAssertNotNil(userFromKeychain.email) 832| 6| XCTAssertNotNil(userFromKeychain.username) 833| 6| XCTAssertNotNil(userFromKeychain.password) 834| 6| XCTAssertNotNil(userFromKeychain.objectId) 835| 6| XCTAssertNotNil(userFromKeychain.sessionToken) 836| 6| XCTAssertNil(userFromKeychain.ACL) 837| 6| 838| 6| } catch { 839| 0| XCTFail(error.localizedDescription) 840| 6| } 841| 6| } 842| | 843| 1| func userLoginAsync(loginResponse: LoginSignupResponse, callbackQueue: DispatchQueue) { 844| 1| 845| 1| let expectation1 = XCTestExpectation(description: "Login user") 846| 1| User.login(username: loginResponse.username!, password: loginResponse.password!, 847| 1| callbackQueue: callbackQueue) { result in 848| 1| 849| 1| switch result { 850| 1| 851| 1| case .success(let loggedIn): 852| 1| XCTAssertNotNil(loggedIn.createdAt) 853| 1| XCTAssertNotNil(loggedIn.updatedAt) 854| 1| XCTAssertNotNil(loggedIn.email) 855| 1| XCTAssertNotNil(loggedIn.username) 856| 1| XCTAssertNotNil(loggedIn.password) 857| 1| XCTAssertNotNil(loggedIn.objectId) 858| 1| XCTAssertNotNil(loggedIn.sessionToken) 859| 1| XCTAssertNotNil(loggedIn.customKey) 860| 1| XCTAssertNil(loggedIn.ACL) 861| 1| 862| 1| guard let userFromKeychain = BaseParseUser.current else { 863| 0| XCTFail("Couldn't get CurrentUser from Keychain") 864| 0| return 865| 1| } 866| 1| 867| 1| XCTAssertNotNil(userFromKeychain.createdAt) 868| 1| XCTAssertNotNil(userFromKeychain.updatedAt) 869| 1| XCTAssertNotNil(userFromKeychain.email) 870| 1| XCTAssertNotNil(userFromKeychain.username) 871| 1| XCTAssertNotNil(userFromKeychain.password) 872| 1| XCTAssertNotNil(userFromKeychain.objectId) 873| 1| XCTAssertNotNil(userFromKeychain.sessionToken) 874| 1| XCTAssertNil(userFromKeychain.ACL) 875| 1| case .failure(let error): 876| 0| XCTFail(error.localizedDescription) 877| 1| } 878| 1| expectation1.fulfill() 879| 1| } 880| 1| wait(for: [expectation1], timeout: 10.0) 881| 1| } 882| | 883| 1| func testLoginAsyncMainQueue() { 884| 1| let loginResponse = LoginSignupResponse() 885| 1| 886| 1| MockURLProtocol.mockRequests { _ in 887| 1| do { 888| 1| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 889| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 890| 1| } catch { 891| 0| return nil 892| 0| } 893| 0| } 894| 1| 895| 1| self.userLoginAsync(loginResponse: loginResponse, callbackQueue: .main) 896| 1| } 897| | 898| 1| func testUserLogout() { 899| 1| let loginResponse = LoginSignupResponse() 900| 1| 901| 1| MockURLProtocol.mockRequests { _ in 902| 1| do { 903| 1| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 904| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 905| 1| } catch { 906| 0| return nil 907| 0| } 908| 0| } 909| 1| do { 910| 1| try User.logout() 911| 1| if let userFromKeychain = BaseParseUser.current { 912| 0| XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") 913| 0| return 914| 1| } 915| 1| 916| 1| } catch { 917| 0| XCTFail(error.localizedDescription) 918| 1| } 919| 1| } 920| | 921| 1| func logoutAsync(callbackQueue: DispatchQueue) { 922| 1| 923| 1| let expectation1 = XCTestExpectation(description: "Logout user1") 924| 1| User.logout(callbackQueue: callbackQueue) { result in 925| 1| 926| 1| switch result { 927| 1| 928| 1| case .success(let success): 929| 1| XCTAssertTrue(success) 930| 1| if let userFromKeychain = BaseParseUser.current { 931| 0| XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") 932| 0| expectation1.fulfill() 933| 0| return 934| 1| } 935| 1| case .failure(let error): 936| 0| XCTFail(error.localizedDescription) 937| 1| } 938| 1| expectation1.fulfill() 939| 1| } 940| 1| wait(for: [expectation1], timeout: 10.0) 941| 1| } 942| | 943| 1| func testLogoutAsyncMainQueue() { 944| 1| let loginResponse = LoginSignupResponse() 945| 1| 946| 1| MockURLProtocol.mockRequests { _ in 947| 1| do { 948| 1| let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) 949| 1| return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) 950| 1| } catch { 951| 0| return nil 952| 0| } 953| 0| } 954| 1| 955| 1| self.logoutAsync(callbackQueue: .main) 956| 1| } 957| | 958| 1| func testUserCustomValuesNotSavedToKeychain() { 959| 1| testUserLogin() 960| 1| User.current?.customKey = "Changed" 961| 1| User.saveCurrentUserContainer() 962| 1| guard let keychainUser: CurrentUserContainer 963| 1| = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else { 964| 0| XCTFail("Should get object from Keychain") 965| 0| return 966| 1| } 967| 1| XCTAssertNil(keychainUser.currentUser?.customKey) 968| 1| } 969| |} // swiftlint:disable:this file_length <<<<<< EOF # path=fixes ./ParseSwift-watchOS/ParseSwift_watchOS.h:8,10,13,16,18,19 ./ParseSwift.playground/Sources/Common.swift:3,9 ./ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift:2,6,9,16,21,24,25,33,39,40,41,48,49,55,58,62,64,67,68,69,71 ./ParseSwift.playground/Pages/3 - User - Sign Up.xcplaygroundpage/Contents.swift:2,6,9,16,21,24,25,31,34,38,39,44,45,48,49,50,52 ./ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift:2,7,10,12,20,23,27,28,29,33,46,57,62,65,66,69,70,71,80,83,84,85,88,89,90,98,99,105,111,113,120,121,125,132,134,141,142,143,145 ./ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift:2,7,9,15,17,18,22,25,32,37,38,41,42,43,50,51,53 ./ParseSwift.playground/Pages/5 - ACL.xcplaygroundpage/Contents.swift:2,6,9,17,18,26,29,34,35,36,39,52,55,56,57,59 ./ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift:2,6,9,16,29,32,33,36,44,50,51,52,53 ./Tests/LinuxMain.swift:2,4 ./Tests/ParseSwiftTests/ParseEncoderTests.swift:8,11,16,17,21,22,29,30,34,38,39,41,42,46,50,51,53,54,59,60,78,82,83 ./Tests/ParseSwiftTests/APICommandTests.swift:8,12,14,20,25,26,31,32,40,41,48,51,52,53,64,65,66,76,78,79,80,98,99,109,112,113,114,121,131,133,134,135,141,151,153,154,155,156 ./Tests/ParseSwiftTests/ParseUserCommandTests.swift:8,12,14,21,26,29,30,37,42,45,56,57,58,64,69,70,75,76,92,93,94,99,112,115,116,124,129,135,136,144,149,155,156,157,163,167,168,172,181,184,185,193,198,202,205,211,213,216,217,218,224,228,229,233,242,245,246,249,257,262,266,269,275,279,281,283,284,287,290,292,300,306,312,314,315,318,320,328,334,340,342,344,345,350,363,366,367,370,371,372,376,384,385,391,399,400,406,410,411,415,424,427,428,436,441,445,448,454,456,459,460,461,467,471,472,476,485,488,489,492,500,505,509,512,518,522,524,526,527,535,538,547,550,557,562,568,569,576,581,587,588,589,592,595,597,604,610,616,618,619,622,624,631,637,643,645,647,648,656,667,670,671,674,675,676,684,695,698,699,701,702,705,712,713,726,730,731,740,743,744,745,747,752,763,767,768,779,781,783,784,787,794,795,796,798,799,802,809,810,823,827,828,837,840,841,842,844,848,850,861,865,866,877,879,881,882,885,892,893,894,896,897,900,907,908,914,915,918,919,920,922,925,927,934,937,939,941,942,945,952,953,954,956,957,966,968,969 ./Tests/ParseSwiftTests/AnyCodableTests/AnyEncodableTests.swift:3,25,27,37,38,42,47,51,52,56 ./Tests/ParseSwiftTests/AnyCodableTests/AnyCodableTests.swift:3,7,17,18,22,31,39,40,61,63,73,74,78,83,87,88,93 ./Tests/ParseSwiftTests/AnyCodableTests/AnyDecodableTests.swift:3,7,17,18,22,31,39,40,44 ./Tests/ParseSwiftTests/ParseQueryTests.swift:8,12,14,21,24,28,29,30,36,41,42,47,48,52,56,60,64,65,72,80,81,82,85,89,93,94,95,96,101,103,109,113,115,117,118,125,133,134,135,138,139,140,147,155,156,158,159,166,174,175,176,179,183,187,188,189,190,195,197,200,203,205,207,208,215,223,224,225,228,229,230,237,245,246,248,249,256,264,265,266,269,274,275,276,277,282,284,289,291,293,294,301,309,310,311,314,315,316,323,331,332,334,335 ./Tests/ParseSwiftTests/ParseInstallationTests.swift:8,17,19,26,31,34,35,42,47,50,61,62,63,81,82,84,90,96,97,102,103,106,113,114,120,121,122,131,132,134,139,140,144,146,147,155,156,160,162,163,170,172,173,191,192,202,213,215,216,235,236,246,248,253,264,266,267,277,285,289,291,292,299,302,311,314,324,330,336,338,340,341,350,353,355,356,359,362,364,371,377,381,389,395,400,407,413,418,419,423,424,426,427,431,437,445,452,455,456,458,459,463,471,473,476,486,489,490,499,506,510,516,518,526,528,531,532,534,536,537,541,548,549,552,562,565,566,568,577,584,588,594,596,604,608,610,611,613,614 ./Tests/ParseSwiftTests/NetworkMocking/MockURLProtocol.swift:8,10,13,18,19,26,27,30,31,35,36,43,44,45,49,51,52,57,58,60,61,64,65,69,71,72,75,76,83,85,86,92,93,96,99,100,101,103,104,109,110,112,115,116,120,122,123,124,125,128,129 ./Tests/ParseSwiftTests/NetworkMocking/MockURLResponse.swift:8,11,18,24,25,28,29,32,38,39,40,48,49 ./Tests/ParseSwiftTests/ParseObjectCommandTests.swift:8,12,14,21,24,28,29,30,36,41,42,47,48,64,65,66,72,85,86,89,97,102,108,109,117,122,128,129,130,133,136,145,151,157,159,160,163,172,178,184,186,188,189,194,199,208,209,212,213,216,217,218,223,236,237,240,242,243,247,255,256,262,270,271,274,280,289,290,293,301,306,312,313,321,326,332,333,334,341,344,353,354,357,364,369,375,376,383,388,394,395,396,399,401,403,405,413,419,425,427,428,431,433,441,447,453,455,457,458,461,475,478,479,482,483,484,487,501,504,505,507,508,511,513,515,517,524,530,536,538,539,542,544,551,557,563,565,567,568,575,586,589,590,593,594,595,602,613,616,618,619 ./Tests/ParseSwiftTests/ACLTests.swift:8,12,14,20,25,26,30,31,38,43,46,47,54,59,62,73,74,75,80,83,86,87,92,95,98,101,102,107,110,113,116,117,124,130,131,141,144,145,146,147,158,161,162,168,171,172,173,176,183,184,185,191,192,196,197,210,213,214,215,218,225,226,231,232,236,237,248,249,250 ./Tests/ParseSwiftTests/KeychainStoreTests.swift:8,12,18,19,23,24,27,28,36,38,39,47,49,50,56,57,63,64,70,71,77,78,86,88,89,95,104,110,116,119,120,121,122,129,130,137,138,149,150,154,155,156,161,162,163,169,170,171 ./Tests/ParseSwiftTests/ParseObjectBatchTests.swift:8,12,14,21,24,28,29,30,36,41,42,47,48,52,58,64,75,79,82,83,86,89,96,101,107,108,110,117,122,128,129,132,133,138,145,150,156,157,159,166,171,177,180,181,182,186,192,198,205,206,207,210,214,217,218,221,225,228,229,230,237,243,248,259,263,266,267,270,273,275,280,285,286,290,293,294,296,298,303,308,309,313,316,317,320,321,325,327,333,338,339,345,346,348,354,359,368,369,372,373,374,381,387,392,399,400,406,409,410,413,417,420,421,422,430,436,439,442,451,455,458,459,462,465,472,477,483,484,486,492,497,503,504,507,508,513,520,521,523,530,533,534,535,538,544,545,547,549,557,558,560,568,574,578,581,582,584,592,598,602,605,606,609,611,612,615,617,620,626,627,629,636,642,648,649,651,658,664,670,671,674,676,678,679,683,689,695,706,710,713,714,718,719,720,724,730,736,747,751,754,757,758,763,765,767,769,776,777,779,786,792,793,799,800,802,809,815,816,823,824,827,829,830,833,835,842,843,845,852,858,859,865,866,868,875,881,882,889,890,893,895,897,898,905,911,916,919,928,932,935,936,939,940,941,948,954,959,962,971,975,978,980,981 ./ParseSwift-tvOS/ParseSwift_tvOS.h:8,10,13,16,18,19 ./Package.swift:2,4 ./Sources/ParseSwift/Mutation Operations/ParseMutationContainer.swift:8,10,13,16,19,20,23,24,27,28,35,36,43,44,47,48,55,56,63,64,67,68,76,78,79,87,89,90,93,94,98,99,106,107,108 ./Sources/ParseSwift/Mutation Operations/DeleteOperation.swift:8,10,13 ./Sources/ParseSwift/Mutation Operations/AddOperation.swift:8,10,14 ./Sources/ParseSwift/Mutation Operations/AddUniqueOperation.swift:8,10,14 ./Sources/ParseSwift/Mutation Operations/RemoveOperation.swift:8,10,14 ./Sources/ParseSwift/Mutation Operations/IncrementOperation.swift:8,10,14 ./Sources/ParseSwift/Parse.h:8,10,13,16,18,19 ./Sources/ParseSwift/Objects/ParseUser.swift:2,13,18,21,26,27,32,33,34,39,40,46,47,53,54,56,57,60,66,67,70,75,76,77,80,83,86,90,98,99,103,119,120,127,133,140,141,142,143,146,152,153,156,160,168,169,170,175,176,177,178,183,185,187,193,194,197,199,201,206,207,210,212,221,222,225,227,229,244,245,248,256,263,264,265,272,279,280,281,282,289,290,295 ./Sources/ParseSwift/Objects/ParseInstallation.swift:8,10,16,23,30,33,38,43,48,53,58,63,68,70,72,74,76,78,79,84,85,86,91,92,108,109,110,113,114,115,122,125,126,132,133,135,136,139,146,150,151,152,153,162,163,167,168,169,171,174,175,176,181,182,183,186,194,199,204,205,206,210,211,221,222,223,228,229,231,235,236,237,241,242,243,246,247,248,251,256,262,263,269,270,273,274,275 ./Sources/ParseSwift/Objects/ParseObject.swift:8,10,20,25,30,35,40,41,50,51,57,58,61,63,68,69,70,73,76,78,87,88,91,106,107,108,114,115,117,118,121,122,123,130,131,133,134,135,141,142,148,159,166,173,174,175,176,179,187,188,191,207,209,214,215,216,219,220,221,226,227,228,231,234,235,238,239,242,243,244,247,250,253,260,261,264,278,280,281,282,285,286,287,291,292 ./Sources/ParseSwift/Objects/Protocols/Fetchable.swift:8,11,14,15,19,20 ./Sources/ParseSwift/Objects/Protocols/Saveable.swift:8,11,14,15,19,20 ./Sources/ParseSwift/Objects/Protocols/Queryable.swift:9,12,22,23,27,29,34,35,38,40,42,47,48,51,53,58,59,62,69,70,73,75,84,85,88,95,96 ./Sources/ParseSwift/Storage/ParseStorage.swift:7,11,14,19,20 ./Sources/ParseSwift/Storage/SecureStorage.swift:8,10,18 ./Sources/ParseSwift/Storage/KeychainStore.swift:8,10,15,19,20,24,26,34,35,40,41,47,53,54,55,59,66,70,73,74,78,79,80,84,87,88,89,93,94,95,100,105,107,109,114,115,117,118,119,124,128,129,133,134,136,137,141,142,143,148,151,152,153,157,160,161,162 ./Sources/ParseSwift/Storage/PrimitiveObjectStore.swift:7,9,16,17,25,28,29,32,33,37,38,42,43,44,47,51,52,53,57,58,59,62,63,68,69,70 ./Sources/ParseSwift/ParseConstants.swift:8,10,22 ./Sources/ParseSwift/API/URLSession+extensions.swift:9,11,13,28,34,35,36,39,44,46,47 ./Sources/ParseSwift/API/API+Commands.swift:8,10,12,20,31,32,35,41,43,47,49,50,56,61,63,68,69,77,79,81,84,87,90,91,92,93,96,97,98,99,105,107,108,113,118,119,123,128,129,134,135,141,142,143,144,146,150,151,157,160,164,166,178,179,181,186,188,189,192,193 ./Sources/ParseSwift/API/BatchUtils.swift:8,10,16,19,20,24,25,30,34,36,37,41,43,44,48,50,51,62,63,64 ./Sources/ParseSwift/API/Responses.swift:8,10,16,17,24,25,26,29,34,35,36,40,46,47 ./Sources/ParseSwift/API/API.swift:8,10,12,15,16,25,42,43,44,48,49,50,52,57,67,68,69,70,76,77,80,81,84,85,94,95,96,98,99,100,105,106,107 ./Sources/ParseSwift/Parse Types/Query.swift:8,10,32,33,37,44,45,46,47,50,51,54,55,58,59,62,63,66,67,72,73,78,79,82,83,84,87,88,91,96,97,106,107,108,109,110,125,127,130,136,144,145,146,147,150,151,154,155,159,160,164,170,171,178,179,186,187,194,195,201,202,208,209,212,213,224,225,226,228,230,233,236,243,244,247,258,260,261,262,265,267,270,277,279,280,283,285,296,302,307,308,309,310,313,316,321,322,325,334,335,336,341,342,343,349,350,351,358,359,360,361,368,369,372,375,378,379 ./Sources/ParseSwift/Parse Types/GeoPoint.swift:2,14,15 ./Sources/ParseSwift/Parse Types/Pointer.swift:2,6,8,9,12,16,20,21,25,26,29,30,31,39,40,48,49 ./Sources/ParseSwift/Parse Types/ParseError.swift:8,10,14,17,18,22,23,33,227,236,241,246,252,257,264,269,277,278,279,281,286,287,292,293 ./Sources/ParseSwift/Parse Types/Internal/BaseParseUser.swift:7,9,19 ./Sources/ParseSwift/Parse Types/Internal/BaseParseInstallation.swift:8,10,27,31,32 ./Sources/ParseSwift/Parse Types/Internal/NoBody.swift:8 ./Sources/ParseSwift/Parse Types/Internal/FindResult.swift:8,12 ./Sources/ParseSwift/Parse Types/ACL.swift:8,10,21,28,31,32,36,37,38,40,47,50,51,52,59,62,63,64,74,76,77,82,88,89,94,96,101,102,105,111,112,115,121,122,126,128,133,134,138,140,145,146,149,155,156,159,165,166,169,170,175,179,186,189,190,191,192,193,198,202,206,213,214,218,219,221,222,223,225,226,229,232,235,241,245,247,253,254,262,263,265,267,268,272,276,278,279,280,293,295,298,299,300,311,312,313,314,315,316,322,324,325,326,331 ./Sources/ParseSwift/Parse Types/File.swift:2,4,8,12,13,19,20,24,29,33,34,35,38,39,44,45 ./Sources/ParseSwift/Coding/ParseCoding.swift:8,10,13,17,22,23,28,29,35,36,37,43,44,51,53,59,60,65,73,76,80,87,88,90 ./Sources/ParseSwift/Coding/AnyDecodable.swift:2,5,8,12,14,24,25,27,35,36,37,41,42,44,48,68,69,70,71,111,112,113,114,124,125,126,127,135,136,137 ./Sources/ParseSwift/Coding/ParseEncoder.swift:8,11,13,19,28,29,33,37,38,42,43,47,48,49,56,61,62,69,71,72,79,80,87,88,102,104,105,106,107,113,118,119,122,124,125,128,134,135,145,147,148,155,156,159,160,163,164,165,171,176,177,180,181,184,185,188,189,190,196,201,202,204,205,207,208,211,212,215,216,221,223,224,227,228,232,233,239,245,247,248,252,258,259,262,263 ./Sources/ParseSwift/Coding/Extensions.swift:7,9,14,15,18,19,20,26,27,29,30,31,36,37,40,41 ./Sources/ParseSwift/Coding/AnyCodable.swift:2,5,8,12,15,20,23,27,28,32,33,34,36,76,77,78,79,89,90,91,92,100,101,102,103 ./Sources/ParseSwift/Coding/AnyEncodable.swift:2,5,8,12,25,28,34,38,39,43,44,45,49,52,53,55,57,64,65,110,111,138,139,140,141,181,182,183,184,194,195,196,197,205,206,207,208,216,220,221,224,225,228,229,232,233,236,239,240,243,244,247,248 ./Sources/ParseSwift/Parse.swift:2,9,10,26,29,32,33 ./TestHost/AppDelegate.swift:8,10,13 <<<<<< EOF