diff --git a/CHANGELOG.md b/CHANGELOG.md index e947255d8..76d49abc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ ### main -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.5...main) +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.6...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +### 2.2.6 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.5...2.2.6) + +__Fixes__ +- Use default ACL automatically on newley created ParseObject's if a default ACL is available ([#283](https://github.com/parse-community/Parse-Swift/pull/283)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 2.2.5 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.4...2.2.5) diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 02c12c68f..f819d2a74 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -101,6 +101,9 @@ 70170A4E2656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70170A4D2656EBA50070C905 /* ParseAnalyticsTests.swift */; }; 70170A4F2656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70170A4D2656EBA50070C905 /* ParseAnalyticsTests.swift */; }; 70170A502656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70170A4D2656EBA50070C905 /* ParseAnalyticsTests.swift */; }; + 7023800F2747FCCD00EFC443 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7023800E2747FCCD00EFC443 /* ExtensionsTests.swift */; }; + 702380102747FCCD00EFC443 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7023800E2747FCCD00EFC443 /* ExtensionsTests.swift */; }; + 702380112747FCCD00EFC443 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7023800E2747FCCD00EFC443 /* ExtensionsTests.swift */; }; 7028373426BD8883007688C9 /* ParseObject+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7028373326BD8883007688C9 /* ParseObject+async.swift */; }; 7028373526BD8883007688C9 /* ParseObject+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7028373326BD8883007688C9 /* ParseObject+async.swift */; }; 7028373626BD8883007688C9 /* ParseObject+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7028373326BD8883007688C9 /* ParseObject+async.swift */; }; @@ -828,6 +831,7 @@ 70170A432656B02C0070C905 /* ParseAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnalytics.swift; sourceTree = ""; }; 70170A482656E2FE0070C905 /* ParseAnalytics+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseAnalytics+combine.swift"; sourceTree = ""; }; 70170A4D2656EBA50070C905 /* ParseAnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnalyticsTests.swift; sourceTree = ""; }; + 7023800E2747FCCD00EFC443 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = ""; }; 7028373326BD8883007688C9 /* ParseObject+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseObject+async.swift"; sourceTree = ""; }; 7028373826BD8C89007688C9 /* ParseUser+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseUser+async.swift"; sourceTree = ""; }; 7033ECB025584A83009770F3 /* TestHostTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestHostTV.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1139,6 +1143,7 @@ children = ( 4AA8076D1F794C1C008CD551 /* Info.plist */, 911DB12D24C4837E0027F3C7 /* APICommandTests.swift */, + 7023800E2747FCCD00EFC443 /* ExtensionsTests.swift */, 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */, 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */, 709B40C0268F999000ED2EAC /* IOS13Tests.swift */, @@ -2233,6 +2238,7 @@ 705A99F9259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 7044C20625C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */, 70C5508525B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */, + 7023800F2747FCCD00EFC443 /* ExtensionsTests.swift in Sources */, 917BA43E2703E84000F8D747 /* ParseOperationAsyncTests.swift in Sources */, 7037DAB226384DE1005D7E62 /* TestParseEncoder.swift in Sources */, 7004C24D25B69207005E0AD9 /* ParseRoleTests.swift in Sources */, @@ -2457,6 +2463,7 @@ 705A99FB259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 7044C20825C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */, 70C5508725B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */, + 702380112747FCCD00EFC443 /* ExtensionsTests.swift in Sources */, 917BA4402703E84000F8D747 /* ParseOperationAsyncTests.swift in Sources */, 7037DAB426384DE1005D7E62 /* TestParseEncoder.swift in Sources */, 7004C26125B6920B005E0AD9 /* ParseRoleTests.swift in Sources */, @@ -2544,6 +2551,7 @@ 705A99FA259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 7044C20725C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */, 70C5508625B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */, + 702380102747FCCD00EFC443 /* ExtensionsTests.swift in Sources */, 917BA43F2703E84000F8D747 /* ParseOperationAsyncTests.swift in Sources */, 7037DAB326384DE1005D7E62 /* TestParseEncoder.swift in Sources */, 7004C25725B6920A005E0AD9 /* ParseRoleTests.swift in Sources */, diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index 77c6d1220..fa46b90ff 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -360,6 +360,11 @@ internal extension API.Command { // MARK: Saving ParseObjects - private private static func create(_ object: T) -> API.Command where T: ParseObject { + var object = object + if object.ACL == nil, + let acl = try? ParseACL.defaultACL() { + object.ACL = acl + } let mapper = { (data) -> T in try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: object) } diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index af1ec4bcf..125c901a9 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -318,32 +318,35 @@ private class _ParseEncoder: JSONEncoder, Encoder { } else { valueToEncode = pointer } - } else if let object = value as? Objectable, - let pointer = try? PointerType(object) { - if let uniquePointer = self.uniquePointer, - uniquePointer.hasSameObjectId(as: pointer) { - throw ParseError(code: .unknownError, - message: "Found a circular dependency when encoding.") - } - if !self.collectChildren && codingPath.count > 0 { - valueToEncode = value + } else if let object = value as? Objectable { + if let pointer = try? PointerType(object) { + if let uniquePointer = self.uniquePointer, + uniquePointer.hasSameObjectId(as: pointer) { + throw ParseError(code: .unknownError, + message: "Found a circular dependency when encoding.") + } + if !self.collectChildren && codingPath.count > 0 { + valueToEncode = value + } else { + valueToEncode = pointer + } } else { - valueToEncode = pointer - } - } else { - let hashOfCurrentObject = try BaseObjectable.createHash(value) - if self.collectChildren { + var object = object + if object.ACL == nil, + let acl = try? ParseACL.defaultACL() { + object.ACL = acl + } + let hashOfCurrentObject = try BaseObjectable.createHash(object) + valueToEncode = object if let pointerForCurrentObject = self.objectsSavedBeforeThisOne?[hashOfCurrentObject] { valueToEncode = pointerForCurrentObject - } else { - //New object needs to be saved before it can be pointed to - self.newObjects.append(value) + } else if self.collectChildren { + // New object needs to be saved before it can be pointed to + self.newObjects.append(object) + } else if dictionary.count > 0 { + // Only top level objects can be saved without a pointer + throw ParseError(code: .unknownError, message: "Error. Couldn't resolve unsaved object while encoding.") } - } else if let pointerForCurrentObject = self.objectsSavedBeforeThisOne?[hashOfCurrentObject] { - valueToEncode = pointerForCurrentObject - } else if dictionary.count > 0 { - //Only top level objects can be saved without a pointer - throw ParseError(code: .unknownError, message: "Error. Couldn't resolve unsaved object while encoding.") } } return valueToEncode diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 14d641b7f..3fa6c7428 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -598,12 +598,17 @@ extension ParseInstallation { // MARK: Saving ParseObjects - private private func createCommand() -> API.Command { + var object = self + if object.ACL == nil, + let acl = try? ParseACL.defaultACL() { + object.ACL = acl + } let mapper = { (data) -> Self in - try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: self) + try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: object) } return API.Command(method: .POST, path: endpoint(.POST), - body: self, + body: object, mapper: mapper) } diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 3ed3c7a06..380c098d1 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -792,10 +792,10 @@ extension ParseObject { try waitingToBeSaved.forEach { parseType in if let parseFile = parseType as? ParseFile { - //ParseFiles can be saved now + // ParseFiles can be saved now savableFiles.append(parseFile) } else if let parseObject = parseType as? Objectable { - //This is a ParseObject + // This is a ParseObject let waitingObjectInfo = try ParseCoding .parseEncoder() .encode(parseObject, diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 7074c1474..93d62d8d1 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -979,12 +979,17 @@ extension ParseUser { // MARK: Saving ParseObjects - private private func createCommand() -> API.Command { + var object = self + if object.ACL == nil, + let acl = try? ParseACL.defaultACL() { + object.ACL = acl + } let mapper = { (data) -> Self in - try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: self) + try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: object) } return API.Command(method: .POST, path: endpoint(.POST), - body: self, + body: object, mapper: mapper) } diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index ba503f8e3..05ce83cc5 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -10,7 +10,7 @@ import Foundation enum ParseConstants { static let sdk = "swift" - static let version = "2.2.5" + static let version = "2.2.6" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" static let fileManagementLibraryDirectory = "Library/" diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index cbe5c279c..fce30f038 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -303,32 +303,38 @@ extension ParseACL { */ public static func defaultACL() throws -> Self { - let aclController: DefaultACL? + let aclController: DefaultACL! #if !os(Linux) && !os(Android) && !os(Windows) - aclController = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.defaultACL) + if let controller: DefaultACL = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.defaultACL) { + aclController = controller + } else { + throw ParseError(code: .unknownError, + message: "Default ACL can't be found in Keychain. You should `setDefaultACL` first") + } #else - aclController = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.defaultACL) + if let controller: DefaultACL = try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.defaultACL) { + aclController = controller + } else { + throw ParseError(code: .unknownError, + message: "Default ACL can't be found in Keychain. You should `setDefaultACL` first") + } #endif - if let acl = aclController { - if !acl.useCurrentUser { - return acl.defaultACL - } else { - guard let userObjectId = BaseParseUser.current?.objectId else { - return acl.defaultACL - } - - guard let lastCurrentUserObjectId = acl.lastCurrentUserObjectId, - userObjectId == lastCurrentUserObjectId else { - return try setDefaultACL(ParseACL(), withAccessForCurrentUser: true) - } + if !aclController.useCurrentUser { + return aclController.defaultACL + } else { + guard let userObjectId = BaseParseUser.current?.objectId else { + return aclController.defaultACL + } - return acl.defaultACL + guard let lastCurrentUserObjectId = aclController.lastCurrentUserObjectId, + userObjectId == lastCurrentUserObjectId else { + return try setDefaultACL(ParseACL(), withAccessForCurrentUser: true) } - } - return try setDefaultACL(ParseACL(), withAccessForCurrentUser: true) + return aclController.defaultACL + } } /** diff --git a/Tests/ParseSwiftTests/ExtensionsTests.swift b/Tests/ParseSwiftTests/ExtensionsTests.swift new file mode 100644 index 000000000..c7f8899d0 --- /dev/null +++ b/Tests/ParseSwiftTests/ExtensionsTests.swift @@ -0,0 +1,42 @@ +// +// ExtensionsTests.swift +// ParseSwift +// +// Created by Corey Baker on 11/19/21. +// Copyright © 2021 Parse Community. All rights reserved. +// + +import Foundation +import XCTest +@testable import ParseSwift + +class ExtensionsTests: XCTestCase { + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + #if !os(Linux) && !os(Android) && !os(Windows) + func testURLSession() throws { + ParseSwift.configuration.isTestingSDK = false + XCTAssertNotNil(URLSession.parse.configuration.urlCache) + } + #endif +} diff --git a/Tests/ParseSwiftTests/ParseACLTests.swift b/Tests/ParseSwiftTests/ParseACLTests.swift index 1e2b827eb..2d64cb86b 100644 --- a/Tests/ParseSwiftTests/ParseACLTests.swift +++ b/Tests/ParseSwiftTests/ParseACLTests.swift @@ -247,6 +247,10 @@ class ParseACLTests: XCTestCase { } } + func testNoDefaultACL() { + XCTAssertThrowsError(try ParseACL.defaultACL()) + } + func testDefaultACL() { let loginResponse = LoginSignupResponse() let loginUserName = "hello10" @@ -276,10 +280,8 @@ class ParseACLTests: XCTestCase { newACL.publicRead = true newACL.publicWrite = true do { - var defaultACL = try ParseACL.defaultACL() - XCTAssertNotEqual(newACL, defaultACL) _ = try ParseACL.setDefaultACL(newACL, withAccessForCurrentUser: true) - defaultACL = try ParseACL.defaultACL() + let defaultACL = try ParseACL.defaultACL() XCTAssertEqual(newACL.publicRead, defaultACL.publicRead) XCTAssertEqual(newACL.publicWrite, defaultACL.publicWrite) XCTAssertTrue(defaultACL.getReadAccess(objectId: userObjectId)) diff --git a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift index 9d522d618..7996555ce 100644 --- a/Tests/ParseSwiftTests/ParseAnalyticsTests.swift +++ b/Tests/ParseSwiftTests/ParseAnalyticsTests.swift @@ -193,7 +193,6 @@ class ParseAnalyticsTests: XCTestCase { func testTrackAppOpenedNotAuthorized() { if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) { ParseSwift.configuration.isTestingSDK = false //Allow authorization check - let expectation = XCTestExpectation(description: "Analytics save") ParseAnalytics.trackAppOpened(dimensions: ["stop": "drop"]) { result in diff --git a/Tests/ParseSwiftTests/ParseInstallationAsyncTests.swift b/Tests/ParseSwiftTests/ParseInstallationAsyncTests.swift index 659e95462..dd9cdb77e 100644 --- a/Tests/ParseSwiftTests/ParseInstallationAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationAsyncTests.swift @@ -156,8 +156,8 @@ class ParseInstallationAsyncTests: XCTestCase { // swiftlint:disable:this type_b } do { - let saved = try Installation.current!.save() - guard let newCurrentInstallation = Installation.current else { + guard let saved = try Installation.current?.save(), + let newCurrentInstallation = Installation.current else { XCTFail("Should have a new current installation") return } diff --git a/Tests/ParseSwiftTests/ParseInstallationCombineTests.swift b/Tests/ParseSwiftTests/ParseInstallationCombineTests.swift index 6b0381f24..3252a17de 100644 --- a/Tests/ParseSwiftTests/ParseInstallationCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationCombineTests.swift @@ -157,8 +157,8 @@ class ParseInstallationCombineTests: XCTestCase { // swiftlint:disable:this type } do { - let saved = try Installation.current!.save() - guard let newCurrentInstallation = Installation.current else { + guard let saved = try Installation.current?.save(), + let newCurrentInstallation = Installation.current else { XCTFail("Should have a new current installation") return } diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index 053fc70b9..7a60fe669 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -94,7 +94,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l masterKey: "masterKey", serverURL: url, testing: true) - userLogin() + try userLogin() } override func tearDownWithError() throws { @@ -106,7 +106,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l try ParseStorage.shared.deleteAll() } - func userLogin() { + func userLogin() throws { let loginResponse = LoginSignupResponse() let loginUserName = "hello10" let loginPassword = "world" @@ -119,12 +119,8 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l return nil } } - do { - _ = try User.login(username: loginUserName, password: loginPassword) - MockURLProtocol.removeAll() - } catch { - XCTFail("Should login") - } + _ = try User.login(username: loginUserName, password: loginPassword) + MockURLProtocol.removeAll() } func testNewInstallationIdentifierIsLowercase() { @@ -299,6 +295,53 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } } + func testUpdateWithDefaultACL() throws { + try userLogin() + _ = try ParseACL.setDefaultACL(ParseACL(), + withAccessForCurrentUser: true) + + var installation = Installation() + installation.objectId = testInstallationObjectId + installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.ACL = nil + installation.installationId = "hello" + + var installationOnServer = installation + installationOnServer.updatedAt = Date() + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + let saved = try installation.save() + XCTAssertTrue(saved.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(saved.hasSameInstallationId(as: installationOnServer)) + guard let savedUpdatedAt = saved.updatedAt else { + XCTFail("Should unwrap dates") + return + } + guard let serverUpdatedAt = installationOnServer.updatedAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(savedUpdatedAt, serverUpdatedAt) + XCTAssertNil(saved.ACL) + } catch { + XCTFail(error.localizedDescription) + } + } + func testSaveCurrentInstallation() throws { guard var installation = Installation.current else { XCTFail("Should unwrap") @@ -325,8 +368,8 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } do { - let saved = try Installation.current!.save() - guard let newCurrentInstallation = Installation.current else { + guard let saved = try Installation.current?.save(), + let newCurrentInstallation = Installation.current else { XCTFail("Should have a new current installation") return } @@ -340,6 +383,59 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } } + func testSaveCurrentInstallationWithDefaultACL() throws { + try userLogin() + guard let userObjectId = User.current?.objectId else { + XCTFail("Should have objectId") + return + } + let defaultACL = try ParseACL.setDefaultACL(ParseACL(), + withAccessForCurrentUser: true) + + guard var installation = Installation.current else { + XCTFail("Should unwrap") + return + } + installation.objectId = testInstallationObjectId + installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.ACL = nil + + var installationOnServer = installation + + let encoded: Data! + do { + encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + guard let saved = try Installation.current?.save(), + let newCurrentInstallation = Installation.current else { + XCTFail("Should have a new current installation") + return + } + XCTAssertTrue(saved.hasSameInstallationId(as: newCurrentInstallation)) + XCTAssertTrue(saved.hasSameObjectId(as: newCurrentInstallation)) + XCTAssertTrue(saved.hasSameObjectId(as: installationOnServer)) + XCTAssertTrue(saved.hasSameInstallationId(as: installationOnServer)) + XCTAssertNotNil(saved.ACL) + XCTAssertEqual(saved.ACL?.publicRead, defaultACL.publicRead) + XCTAssertEqual(saved.ACL?.publicWrite, defaultACL.publicWrite) + XCTAssertTrue(defaultACL.getReadAccess(objectId: userObjectId)) + XCTAssertTrue(defaultACL.getWriteAccess(objectId: userObjectId)) + } catch { + XCTFail(error.localizedDescription) + } + } + // swiftlint:disable:next function_body_length func updateAsync(installation: Installation, installationOnServer: Installation, callbackQueue: DispatchQueue) { diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index dbce8e68c..617c0f354 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -205,6 +205,72 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length } } + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + } + + struct LoginSignupResponse: ParseUser { + + var objectId: String? + var createdAt: Date? + var sessionToken: String? + var updatedAt: Date? + var ACL: ParseACL? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + init() { + let date = Date() + self.createdAt = date + self.updatedAt = date + self.objectId = "yarr" + self.ACL = nil + self.customKey = "blah" + self.sessionToken = "myToken" + self.username = "hello10" + self.email = "hello@parse.com" + } + } + + func loginNormally() throws -> User { + let loginResponse = LoginSignupResponse() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + let user = try User.login(username: "parse", password: "user") + MockURLProtocol.removeAll() + return user + } + override func setUpWithError() throws { try super.setUpWithError() guard let url = URL(string: "http://localhost:1337/1") else { @@ -240,6 +306,21 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length wait(for: [expectation2], timeout: 20.0) } + func testIsEqualExtension() throws { + let score1 = GameScore(score: 2) + let score2 = GameScore(score: 3) + XCTAssertFalse(score1.isEqual(score2)) + } + + func testId() throws { + var score = GameScore() + let objectId = "yolo" + XCTAssertNotNil(UUID(uuidString: score.id)) + XCTAssertNotEqual(score.id, objectId) + score.objectId = "yolo" + XCTAssertEqual(score.id, objectId) + } + func testParseObjectMutable() throws { var score = GameScore(score: 19, name: "fire") score.objectId = "yolo" @@ -676,6 +757,60 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length } } + func testSaveWithDefaultACL() throws { // swiftlint:disable:this function_body_length + let user = try loginNormally() + guard let userObjectId = user.objectId else { + XCTFail("Should have objectId") + return + } + let defaultACL = try ParseACL.setDefaultACL(ParseACL(), + withAccessForCurrentUser: true) + + let score = GameScore(score: 10) + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let saved = try score.save() + XCTAssert(saved.hasSameObjectId(as: scoreOnServer)) + guard let savedCreatedAt = saved.createdAt, + let savedUpdatedAt = saved.updatedAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = scoreOnServer.createdAt, + let originalUpdatedAt = scoreOnServer.updatedAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(savedCreatedAt, originalCreatedAt) + XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) + XCTAssertNotNil(saved.ACL) + XCTAssertEqual(saved.ACL?.publicRead, defaultACL.publicRead) + XCTAssertEqual(saved.ACL?.publicWrite, defaultACL.publicWrite) + XCTAssertTrue(defaultACL.getReadAccess(objectId: userObjectId)) + XCTAssertTrue(defaultACL.getWriteAccess(objectId: userObjectId)) + } catch { + XCTFail(error.localizedDescription) + } + } + func testUpdate() { var score = GameScore(score: 10) score.objectId = "yarr" @@ -732,6 +867,49 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length } } + func testUpdateWithDefaultACL() throws { + _ = try loginNormally() + _ = try ParseACL.setDefaultACL(ParseACL(), withAccessForCurrentUser: true) + + var score = GameScore(score: 10) + score.objectId = "yarr" + score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.ACL = nil + + var scoreOnServer = score + scoreOnServer.updatedAt = Date() + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let saved = try score.save() + guard let savedUpdatedAt = saved.updatedAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalUpdatedAt = score.updatedAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) + XCTAssertNil(saved.ACL) + } catch { + XCTFail(error.localizedDescription) + } + } + // swiftlint:disable:next function_body_length func saveAsync(score: GameScore, scoreOnServer: GameScore, callbackQueue: DispatchQueue) { @@ -1273,6 +1451,124 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length wait(for: [expectation1], timeout: 20.0) } + // swiftlint:disable:next function_body_length + func testDeepSaveOneDeepWithDefaultACL() throws { + let user = try loginNormally() + guard let userObjectId = user.objectId else { + XCTFail("Should have objectId") + return + } + let defaultACL = try ParseACL.setDefaultACL(ParseACL(), + withAccessForCurrentUser: true) + + let score = GameScore(score: 10) + var game = Game(score: score) + + var scoreOnServer = score + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + scoreOnServer.objectId = "yarr" + + let response = [BatchResponseItem(success: scoreOnServer, error: nil)] + + let encoded: Data! + do { + encoded = try GameScore.getJSONEncoder().encode(response) + //Get dates in correct format from ParseDecoding strategy + let encodedScoreOnServer = try scoreOnServer.getEncoder().encode(scoreOnServer) + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encodedScoreOnServer) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let expectation1 = XCTestExpectation(description: "Deep save") + game.ensureDeepSave { (savedChildren, savedChildFiles, parseError) in + + XCTAssertEqual(savedChildren.count, 1) + XCTAssertEqual(savedChildFiles.count, 0) + var counter = 0 + var savedChildObject: PointerType? + savedChildren.forEach { (_, value) in + XCTAssertEqual(value.className, "GameScore") + XCTAssertEqual(value.objectId, "yarr") + if counter == 0 { + savedChildObject = value + } + counter += 1 + } + XCTAssertNil(parseError) + + guard let savedChild = savedChildObject else { + XCTFail("Should have unwrapped child object") + expectation1.fulfill() + return + } + + //Saved updated info for game + let encodedScore: Data + do { + encodedScore = try ParseCoding.jsonEncoder().encode(savedChild) + //Decode Pointer as GameScore + game.score = try game.getDecoder().decode(GameScore.self, from: encodedScore) + } catch { + XCTFail("Should encode/decode. Error \(error)") + expectation1.fulfill() + return + } + + //Setup ParseObject to return from mocker + MockURLProtocol.removeAll() + + var gameOnServer = game + gameOnServer.objectId = "nice" + gameOnServer.createdAt = Date() + gameOnServer.updatedAt = gameOnServer.createdAt + + let encodedGamed: Data + do { + encodedGamed = try game.getEncoder().encode(gameOnServer, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + gameOnServer = try game.getDecoder().decode(Game.self, from: encodedGamed) + } catch { + XCTFail("Should encode/decode. Error \(error)") + expectation1.fulfill() + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encodedGamed, statusCode: 200, delay: 0.0) + } + + guard let savedGame = try? game + .saveCommand() + .execute(options: [], + callbackQueue: .main, + childObjects: savedChildren, + childFiles: savedChildFiles) else { + XCTFail("Should have saved game") + expectation1.fulfill() + return + } + XCTAssertEqual(savedGame.objectId, gameOnServer.objectId) + XCTAssertEqual(savedGame.createdAt, gameOnServer.createdAt) + XCTAssertEqual(savedGame.updatedAt, gameOnServer.updatedAt) + XCTAssertEqual(savedGame.score, gameOnServer.score) + XCTAssertNotNil(savedGame.ACL) + XCTAssertEqual(savedGame.ACL?.publicRead, defaultACL.publicRead) + XCTAssertEqual(savedGame.ACL?.publicWrite, defaultACL.publicWrite) + XCTAssertTrue(defaultACL.getReadAccess(objectId: userObjectId)) + XCTAssertTrue(defaultACL.getWriteAccess(objectId: userObjectId)) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + func testDeepSaveDetectCircular() throws { let score = GameScoreClass(score: 10) let game = GameClass(score: score) diff --git a/Tests/ParseSwiftTests/ParseRoleTests.swift b/Tests/ParseSwiftTests/ParseRoleTests.swift index eeee80138..cae2e6599 100644 --- a/Tests/ParseSwiftTests/ParseRoleTests.swift +++ b/Tests/ParseSwiftTests/ParseRoleTests.swift @@ -112,16 +112,22 @@ class ParseRoleTests: XCTestCase { } func testName() throws { - XCTAssertNoThrow(try Role(name: "Hello9_- ")) + let role1 = try Role(name: "Hello9_- ") + let role2 = try Role(name: "Hello9_- ", acl: ParseACL()) + let roles = [role1: "hello", + role2: "world"] + XCTAssertEqual(role1, role1) + XCTAssertNotEqual(role1, role2) + XCTAssertEqual(roles[role1], "hello") + XCTAssertEqual(roles[role2], "world") XCTAssertThrowsError(try Role(name: "Hello9!")) - XCTAssertNoThrow(try Role(name: "Hello9_- ", acl: ParseACL())) XCTAssertThrowsError(try Role(name: "Hello9!", acl: ParseACL())) } func testEndPoint() throws { var role = try Role(name: "Administrator") + XCTAssertEqual(role.endpoint.urlComponent, "/roles") role.objectId = "me" - //This endpoint is at the ParseRole level XCTAssertEqual(role.endpoint.urlComponent, "/roles/me") } diff --git a/Tests/ParseSwiftTests/ParseSessionTests.swift b/Tests/ParseSwiftTests/ParseSessionTests.swift index 5027946b1..b7e492308 100644 --- a/Tests/ParseSwiftTests/ParseSessionTests.swift +++ b/Tests/ParseSwiftTests/ParseSessionTests.swift @@ -94,8 +94,8 @@ class ParseSessionTests: XCTestCase { func testEndPoint() throws { var session = Session() + XCTAssertEqual(session.endpoint.urlComponent, "/sessions") session.objectId = "me" - //This endpoint is at the ParseSession level XCTAssertEqual(session.endpoint.urlComponent, "/sessions/me") } diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index fd45cf78e..21c698d11 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -916,6 +916,102 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } } + func testSaveWithDefaultACL() throws { // swiftlint:disable:this function_body_length + try userSignUp() + guard let userObjectId = User.current?.objectId else { + XCTFail("Should have objectId") + return + } + let defaultACL = try ParseACL.setDefaultACL(ParseACL(), + withAccessForCurrentUser: true) + + let user = User() + var userOnServer = user + userOnServer.objectId = "hello" + userOnServer.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + userOnServer.updatedAt = userOnServer.createdAt + + let encoded: Data! + do { + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + let saved = try user.save(options: [.useMasterKey]) + XCTAssert(saved.hasSameObjectId(as: userOnServer)) + guard let savedCreatedAt = saved.createdAt, + let savedUpdatedAt = saved.updatedAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = userOnServer.createdAt, + let originalUpdatedAt = userOnServer.updatedAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(savedCreatedAt, originalCreatedAt) + XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) + XCTAssertNotNil(saved.ACL) + XCTAssertEqual(saved.ACL?.publicRead, defaultACL.publicRead) + XCTAssertEqual(saved.ACL?.publicWrite, defaultACL.publicWrite) + XCTAssertTrue(defaultACL.getReadAccess(objectId: userObjectId)) + XCTAssertTrue(defaultACL.getWriteAccess(objectId: userObjectId)) + } catch { + XCTFail(error.localizedDescription) + } + } + + func testUpdateWithDefaultACL() throws { // swiftlint:disable:this function_body_length + try userSignUp() + _ = try ParseACL.setDefaultACL(ParseACL(), + withAccessForCurrentUser: true) + var user = User() + let objectId = "yarr" + user.objectId = objectId + user.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + user.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + user.ACL = nil + + var userOnServer = user + userOnServer.updatedAt = Date() + + let encoded: Data! + do { + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let saved = try user.save() + guard let savedUpdatedAt = saved.updatedAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalUpdatedAt = user.updatedAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt) + XCTAssertNil(saved.ACL) + } catch { + XCTFail(error.localizedDescription) + } + } + func updateAsync(user: User, userOnServer: User, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Update user1")