diff --git a/.cirrus.yml b/.cirrus.yml index 927629157..eccdfa58d 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,27 +1,37 @@ task: name: macOS osx_instance: - image: mojave-xcode-11 + image: catalina-xcode-11.4 + xcode_version_script: + - xcode-select -p + - sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + - xcodebuild -version download_dependencies_script: make cache brew_update_script: brew update install_carthage_script: brew install carthage - xcode_version_script: xcodebuild -version - test_macos_script: make test-macos + test_macos_script: make test-macos || (echo System Log; tail -n 1000 /var/log/system.log; echo Simulator Logs; cat ~/Library/Logs/CoreSimulator/*/system.log; echo Xcode Crash Logs; find ~/Library/Developer/Xcode/DerivedData/ -iname *.crash -print -exec cat {} \;) task: name: iOS osx_instance: - image: mojave-xcode-11 + image: catalina-xcode-11.4 + xcode_version_script: + - xcode-select -p + - sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + - xcodebuild -version download_dependencies_script: make cache brew_update_script: brew update install_carthage_script: brew install carthage - xcode_version_script: xcodebuild -version xcode_show_destinations_script: make show-destinations - test_ios_script: make test-ios + test_ios_script: DESTINATION_OS=13.4 DESTINATION_NAME="iPhone 11 Pro" make test-ios || (echo System Log; tail -n 1000 /var/log/system.log; echo Simulator Logs; cat ~/Library/Logs/CoreSimulator/*/system.log; echo Xcode Crash Logs; find ~/Library/Developer/Xcode/DerivedData/ -iname *.crash -print -exec cat {} \;) task: name: macOS SPM osx_instance: - image: mojave-xcode-11 + image: catalina-xcode-11.3.1 + xcode_version_script: + - xcode-select -p + - sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + - xcodebuild -version swift_version_script: swift --version build_script: swift build diff --git a/.travis.yml b/.travis.yml index eb383d426..f65e1f789 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode10.3 +osx_image: xcode11.3 cache: directories: - Carthage @@ -19,9 +19,14 @@ before_script: - brew update - brew install swiftlint || brew upgrade swiftlint - brew install tree || brew upgrade tree -# - brew upgrade carthage - tree -L 2 -P '*.framework' -I '*.dSYM' Carthage/Build - carthage version +# Workaround for error `Segmentation fault: 11` during `carthage checkout` +# downgrade carthage 0.34.0 to 0.33.0 (see issue and comment here: https://github.com/Carthage/Carthage/issues/2760#issuecomment-551499537) +- if carthage version | grep 0.34.0; then + brew uninstall carthage; + brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/d453dd956dbfc5b724b0fdaeb14f9480f461479d/Formula/carthage.rb; + fi - xcpretty --version - if [ ! -d $( md5 Cartfile.resolved | awk '{ print "Carthage/" $4 ".zip" }' ) ]; then make cache; @@ -78,6 +83,13 @@ script: after_script: - date - security delete-keychain ios-build.keychain +after_failure: +- echo System Log +- tail -n 1000 /var/log/system.log +- echo Simulator Logs +- cat ~/Library/Logs/CoreSimulator/*/system.log" +- echo Xcode Crash Logs +- find ~/Library/Developer/Xcode/DerivedData/ -iname *.crash -exec echo "*****" \; -print -exec cat {} \; after_success: - bash <(curl -s https://codecov.io/bash) -J '^Kinvey$' -F $PLATFORM env: @@ -87,4 +99,4 @@ env: - secure: nawAGSUbPzyAH/SllDj9q6IOLGaF91DQsudQYx4AJs+swwFhAvAe3hM6vWdGVuDLxqVHQ4+4AAQFecXq/mDi5WtRISPbGHWVtOx7Q6szL8Y54uZKVmKV06wnAo4nrwu8pp1+TbRLYwG+/oJIfabW2yuMgNn7nWFm5SdE0GuduVI7Ho9p0/SeaE7DCtaNKgyCLfspAA8svCYMK0N9NzBVhkTPzWRiIF174k5VE28lZox/fjN5x4j1aaMBUhElkJELjcWKdTX11MnCyybWjJZ1YTMK/A4vBtZCFmHIFuhtQiqWCQIWxeo8axRDBZEMzZ5U+9zSfaz0w0wovWI/cvtRFS0j/7UQ4o6alO7EOMdhH5rRl8atOxSS+Rl81Oz1kZiLhjJa/feAN5QGQ423L4CyfDeEBV6t1D8Cmb1jlficswFeX2rQw82qFnI2ua9p8HrpUJydDsGR6AoOI+KUamwi53OC+hsG/M1sA4MOAPEJoMGT6dyER5UVE63JB45All9TDJWV2EZz+11FQIp6P/62/QXJBQhqxu+W0u/3q7KqoSgmxFx/QSbv8hpK9aJWDQNf5cv02bT+vJnMv4Lrf+XcSVOWH0TwpL5nVcRJXyq2A6FCGdE2S0Xa0+KkgZdF7p3qk4jN32CryHImRp8T3qbkA6Q9nNdxATt7P9OjYl/XqDA= matrix: - PLATFORM=Mac PLATFORM_ALIAS=osx - - PLATFORM=iOS PLATFORM_ALIAS=ios DESTINATION_OS=12.4 DESTINATION_NAME="iPhone X" + - PLATFORM=iOS PLATFORM_ALIAS=ios DESTINATION_OS=13.3 DESTINATION_NAME="iPhone 11 Pro" diff --git a/Cartfile.resolved b/Cartfile.resolved index 4eb014544..69aeaaa9d 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,12 +1,12 @@ -github "Hearst-DD/ObjectMapper" "3.5.1" -github "Quick/Nimble" "v8.0.4" +github "Hearst-DD/ObjectMapper" "3.5.2" +github "Quick/Nimble" "v8.0.7" github "Quick/Quick" "v2.2.0" -github "SwiftyBeaver/SwiftyBeaver" "1.8.1" -github "httpswift/swifter" "70355c4e414e93a0589a0d5d4ade342534bbab38" -github "kif-framework/KIF" "v3.7.8" -github "kishikawakatsumi/KeychainAccess" "v3.2.0" -github "mxcl/PromiseKit" "6.11.0" -github "pubnub/objective-c" "v4.10.1" -github "realm/realm-cocoa" "v3.19.0" +github "SwiftyBeaver/SwiftyBeaver" "1.9.0" +github "httpswift/swifter" "fbffd02ab42306be4748ec4b8ad58e6c77e09e84" +github "kif-framework/KIF" "v3.7.9" +github "kishikawakatsumi/KeychainAccess" "v3.2.1" +github "mxcl/PromiseKit" "6.13.1" +github "pubnub/objective-c" "v4.13.1" +github "realm/realm-cocoa" "v3.21.0" github "tjboneman/NSPredicate-MongoDB-Adaptor" "fce0cd01913bd4393db0c3dd33404cb7e9ebec88" -github "weichsel/ZIPFoundation" "0.9.9" +github "weichsel/ZIPFoundation" "0.9.11" diff --git a/Kinvey.podspec b/Kinvey.podspec index 0ebf4b48b..09e7e3e38 100644 --- a/Kinvey.podspec +++ b/Kinvey.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "Kinvey" - s.version = "4.1.1" + s.version = "4.1.2" s.summary = "Kinvey iOS SDK" # This description is used to generate tags and improve search results. @@ -92,14 +92,14 @@ Pod::Spec.new do |s| # s.source_files = "Kinvey/Kinvey/**/*.{swift,h,m,mm}", "Carthage/Checkouts/NSPredicate-MongoDB-Adaptor/Sources/MongoDBPredicateAdaptor/*.{swift}" - - # s.public_header_files = - - # s.private_header_files = - - # s.exclude_files = - - # s.prefix_header_file = + + # s.public_header_files = + + # s.private_header_files = + + # s.exclude_files = + + # s.prefix_header_file = # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # @@ -126,7 +126,7 @@ Pod::Spec.new do |s| s.frameworks = "CoreGraphics", "CoreLocation", "Security" s.ios.frameworks = "MobileCoreServices", "WebKit" - # s.libraries = + # s.libraries = # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # @@ -138,7 +138,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.swift_version = '5.0' - + s.dependency "PromiseKit", "~> 6.0" s.dependency "KeychainAccess", "~> 3.0" s.dependency "Realm", "~> 3.17" diff --git a/Kinvey/Kinvey.xcodeproj/project.pbxproj b/Kinvey/Kinvey.xcodeproj/project.pbxproj index e09792eca..77e631310 100644 --- a/Kinvey/Kinvey.xcodeproj/project.pbxproj +++ b/Kinvey/Kinvey.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 3919CCFE243680ED009366D4 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1DC1CDCE88900492D6C /* RealmSwift.framework */; }; + 3919CCFF243680F4009366D4 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1DC1CDCE88900492D6C /* RealmSwift.framework */; }; 570423F91F82EC3E00EE5CBD /* KinveyAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 570423F81F82EC3E00EE5CBD /* KinveyAppUITests.swift */; }; 570424041F832C3600EE5CBD /* Swifter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 570424021F832BF000EE5CBD /* Swifter.framework */; }; 570424061F832C4100EE5CBD /* Swifter.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 570424021F832BF000EE5CBD /* Swifter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -60,7 +62,6 @@ 5728212F1C63E10700373EC8 /* FileStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5728212E1C63E10700373EC8 /* FileStore.swift */; }; 5728213B1C6482C000373EC8 /* URLSessionTaskRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5728213A1C6482C000373EC8 /* URLSessionTaskRequest.swift */; }; 572B948020D235D000E23F59 /* ObjectMapper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B7687C1D10C01F0086AA38 /* ObjectMapper.framework */; }; - 572B948120D235DE00E23F59 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1DC1CDCE88900492D6C /* RealmSwift.framework */; }; 572B948220D235E300E23F59 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1DA1CDCE88900492D6C /* Realm.framework */; }; 572B948320D2A7ED00E23F59 /* PushTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57873DEB1DFF3FDC002C87BF /* PushTestCase.swift */; }; 572C457B1C86690700A41935 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572C457A1C86690700A41935 /* Date.swift */; }; @@ -134,8 +135,6 @@ 575465A41E66405D0063B4B6 /* PerformanceProductTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575465A31E66405D0063B4B6 /* PerformanceProductTestCase.swift */; }; 5754DDBD1EAEBC4A00122A7A /* DateTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5754DDBC1EAEBC4A00122A7A /* DateTestCase.swift */; }; 5756FE861D136C330087141E /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1DC1CDCE88900492D6C /* RealmSwift.framework */; }; - 5756FE871D136C990087141E /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1E91CDCE88900492D6C /* RealmSwift.framework */; }; - 5756FE881D136C9C0087141E /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1E91CDCE88900492D6C /* RealmSwift.framework */; }; 5756FE891D136CAF0087141E /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B0C1DC1CDCE88900492D6C /* RealmSwift.framework */; }; 575772861C4728E5001D56F5 /* TTL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575772851C4728E5001D56F5 /* TTL.swift */; }; 575792C01D412CD20065CC22 /* UserQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575792BF1D412CD20065CC22 /* UserQuery.swift */; }; @@ -1437,7 +1436,7 @@ 57D643041CA3268000F6D16E /* QuartzCore.framework in Frameworks */, 57D643051CA3268000F6D16E /* CoreGraphics.framework in Frameworks */, 57D643071CA3268000F6D16E /* Kinvey.framework in Frameworks */, - 5756FE881D136C9C0087141E /* RealmSwift.framework in Frameworks */, + 3919CCFE243680ED009366D4 /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1445,13 +1444,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3919CCFF243680F4009366D4 /* RealmSwift.framework in Frameworks */, 57D6432B1CA3268600F6D16E /* QuartzCore.framework in Frameworks */, 57D6432C1CA3268600F6D16E /* CoreGraphics.framework in Frameworks */, - 572B948120D235DE00E23F59 /* RealmSwift.framework in Frameworks */, 572B948020D235D000E23F59 /* ObjectMapper.framework in Frameworks */, 572B948220D235E300E23F59 /* Realm.framework in Frameworks */, 57D6432E1CA3268600F6D16E /* Kinvey.framework in Frameworks */, - 5756FE871D136C990087141E /* RealmSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4871,6 +4869,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -4915,6 +4915,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -4923,6 +4924,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/Kinvey/Kinvey/File.swift b/Kinvey/Kinvey/File.swift index 144327a8d..cf447c7d0 100644 --- a/Kinvey/Kinvey/File.swift +++ b/Kinvey/Kinvey/File.swift @@ -11,8 +11,8 @@ import RealmSwift import Realm /// Class that represents a file in the backend holding all metadata of the file, but don't hold the data itself. -open class File: Object { - +open class File: Object, JSONCodable { + /// `_id` property of the file. @objc open dynamic var fileId: String? @@ -132,7 +132,9 @@ open class File: Object { } public enum FileCodingKeys: String, CodingKey { - + case entityId = "_id" + case acl = "_acl" + case metadata = "_kmd" case publicAccessible = "_public" case fileName = "_filename" case mimeType @@ -143,12 +145,79 @@ open class File: Object { case uploadHeaders = "_requiredHeaders" } + + public required init() { + super.init() + } + + public required init(realm: RLMRealm, schema: RLMObjectSchema) { + super.init(realm: realm, schema: schema) + } + + public required init(value: Any, schema: RLMSchema) { + super.init(value: value, schema: schema) + } + + public init(from decoder: Decoder) throws { + super.init() + + let container = try decoder.container(keyedBy: FileCodingKeys.self) + fileId = try container.decodeIfPresent(String.self, forKey: .entityId) + acl = try container.decodeIfPresent(Acl.self, forKey: .acl) + metadata = try container.decodeIfPresent(Metadata.self, forKey: .metadata) + publicAccessible = try container.decodeIfPresent(Bool.self, forKey: .publicAccessible) ?? false + fileName = try container.decodeIfPresent(String.self, forKey: .fileName) + mimeType = try container.decodeIfPresent(String.self, forKey: .mimeType) + size.value = try container.decodeIfPresent(Int64.self, forKey: .size) + upload = try container.decodeIfPresent(String.self, forKey: .upload) + download = try container.decodeIfPresent(String.self, forKey: .download) + expiresAt = try container.decodeIfPresent(Date.self, forKey: .expiresAt) + uploadHeaders = try container.decodeIfPresent([String : String].self, forKey: .uploadHeaders) + } + + open func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: FileCodingKeys.self) + + try container.encodeIfPresent(fileId, forKey: .entityId) + try container.encodeIfPresent(acl, forKey: .acl) + try container.encodeIfPresent(metadata, forKey: .metadata) + try container.encodeIfPresent(publicAccessible, forKey: .publicAccessible) + try container.encodeIfPresent(fileName, forKey: .fileName) + try container.encodeIfPresent(mimeType, forKey: .mimeType) + try container.encodeIfPresent(size, forKey: .size) + try container.encodeIfPresent(upload, forKey: .upload) + try container.encodeIfPresent(download, forKey: .download) + try container.encodeIfPresent(expiresAt, forKey: .expiresAt) + try container.encodeIfPresent(uploadHeaders, forKey: .uploadHeaders) + + } + + open class func decodeArray(from data: Data) throws -> [T] where T : JSONDecodable { + return try decodeArrayJSONDecodable(from: data) + } + + open class func decode(from data: Data) throws -> T where T: JSONDecodable { + return try decodeJSONDecodable(from: data) + } + open class func decode(from dictionary: [String : Any]) throws -> T where T : JSONDecodable { + return try decodeJSONDecodable(from: dictionary) + } + + open func refresh(from dictionary: [String : Any]) throws { + var _self = self + try _self.refreshJSONDecodable(from: dictionary) + } + + open func encode() throws -> [String : Any] { + return try encodeJSONEncodable() + } + @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") open func mapping(map: Map) { - fileId <- ("fileId", map[Entity.EntityCodingKeys.entityId]) - acl <- ("acl", map[Entity.EntityCodingKeys.acl]) - metadata <- ("metadata", map[Entity.EntityCodingKeys.metadata]) + fileId <- ("fileId", map[FileCodingKeys.entityId]) + acl <- ("acl", map[FileCodingKeys.acl]) + metadata <- ("metadata", map[FileCodingKeys.metadata]) publicAccessible <- ("publicAccessible", map[FileCodingKeys.publicAccessible]) fileName <- ("fileName", map[FileCodingKeys.fileName]) mimeType <- ("mimeType", map[FileCodingKeys.mimeType]) diff --git a/Kinvey/Kinvey/FileStore.swift b/Kinvey/Kinvey/FileStore.swift index fc6974bdc..5049649dd 100644 --- a/Kinvey/Kinvey/FileStore.swift +++ b/Kinvey/Kinvey/FileStore.swift @@ -333,18 +333,21 @@ open class FileStore { ) let promise = Promise { resolver in request.execute() { (data, response, error) -> Void in - if let response = response, response.isOK, - let data = data, - let json = try? self.client.jsonParser.parseDictionary(from: data), - let newFile = FileType(JSON: json) { - newFile.path = file.path - if let cache = self.cache { - cache.save(newFile, beforeSave: nil) + do { + if let response = response, response.isOK, + let data = data { + let newFile : FileType = try self.client.jsonParser.parseObject(FileType.self, from: data) + newFile.path = file.path + if let cache = self.cache { + cache.save(newFile, beforeSave: nil) + } + + resolver.fulfill(newFile) + } else { + resolver.reject(buildError(data, response, error, self.client)) } - - resolver.fulfill(newFile) - } else { - resolver.reject(buildError(data, response, error, self.client)) + } catch let err { + resolver.reject(buildError(data, response, err, self.client)) } } if let requests = requests { @@ -516,14 +519,16 @@ open class FileStore { let request = self.client.networkRequestFactory.blob.buildBlobUploadFile(file, options: options) requests += request request.execute { (data, response, error) -> Void in - if let response = response, response.isOK, - let data = data, - let json = try? self.client.jsonParser.parseDictionary(from: data), - let newFile = FileType(JSON: json) - { - resolver.fulfill((file: newFile, skip: nil)) - } else { - resolver.reject(buildError(data, response, error, self.client)) + do { + if let response = response, response.isOK, + let data = data { + let newFile = try self.client.jsonParser.parseObject(FileType.self, from: data) + resolver.fulfill((file: newFile, skip: nil)) + } else { + resolver.reject(buildError(data, response, error, self.client)) + } + } catch let err { + resolver.reject(buildError(data, response, err, self.client)) } } } diff --git a/Kinvey/Kinvey/HttpRequestFactory.swift b/Kinvey/Kinvey/HttpRequestFactory.swift index b3fabc8e1..3b3c75873 100644 --- a/Kinvey/Kinvey/HttpRequestFactory.swift +++ b/Kinvey/Kinvey/HttpRequestFactory.swift @@ -594,8 +594,7 @@ struct HttpBlobRequestFactory: BlobRequestFactory { credential: client.activeUser, options: options ) - - let bodyObject = file.toJSON() + let bodyObject = try! client.jsonParser.toJSON(file) request.request.setValue(file.mimeType ?? "application/octet-stream", forHTTPHeaderField: "X-Kinvey-Content-Type") request.setBody(json: bodyObject) return request diff --git a/Kinvey/Kinvey/Info.plist b/Kinvey/Kinvey/Info.plist index 76d0c69e8..438715f90 100644 --- a/Kinvey/Kinvey/Info.plist +++ b/Kinvey/Kinvey/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 4.1.1 + 4.1.2 CFBundleSignature ???? CFBundleVersion diff --git a/Kinvey/Kinvey/Keychain.swift b/Kinvey/Kinvey/Keychain.swift index c1cce18d6..fa7de0c9e 100644 --- a/Kinvey/Kinvey/Keychain.swift +++ b/Kinvey/Kinvey/Keychain.swift @@ -33,8 +33,8 @@ class Keychain { self._client = client #if os(macOS) var appKey = appKey - if let xcTestConfigurationFilePath = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] { - appKey += URL(fileURLWithPath: xcTestConfigurationFilePath).lastPathComponent + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + appKey += URL(fileURLWithPath: cacheBasePath).lastPathComponent } #endif self.keychain = KeychainAccess.Keychain(service: "com.kinvey.Kinvey.\(appKey)").accessibility(.afterFirstUnlockThisDeviceOnly) diff --git a/Kinvey/Kinvey/Kinvey.swift b/Kinvey/Kinvey/Kinvey.swift index b12dbb666..414af69ac 100644 --- a/Kinvey/Kinvey/Kinvey.swift +++ b/Kinvey/Kinvey/Kinvey.swift @@ -289,9 +289,23 @@ let groupId = "_group_" #if os(macOS) let cacheBasePath: String = { if let xcTestConfigurationFilePath = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] { - return URL(fileURLWithPath: xcTestConfigurationFilePath).deletingLastPathComponent().path + let fileManager = FileManager.default + let tmpDir = fileManager.temporaryDirectory + // Resolve symlinks in temp dir path so that later when tests come up with the resolved paths + // no mismatches occur. NSURL's `resolvingSymlinksInPath` doesn't correctly resolve `/var` as + // a link to `/private/var` so use `contentsOfDirectory` as a workaround. + let resolvedTmpDir = try! fileManager.contentsOfDirectory(at: tmpDir.deletingLastPathComponent(), includingPropertiesForKeys: nil) + .filter({ $0.lastPathComponent == tmpDir.lastPathComponent })[0] + + // Inside test runner, use processId as subfolder to enable parallel execution + return resolvedTmpDir + .appendingPathComponent(Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName) + .appendingPathComponent(String(ProcessInfo.processInfo.processIdentifier)) + .path } else { - return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!).appendingPathComponent(Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName).path + return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!) + .appendingPathComponent(Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName) + .path } }() #else diff --git a/Kinvey/Kinvey/ObjectMapperSupport.swift b/Kinvey/Kinvey/ObjectMapperSupport.swift index 824c8b2f8..e90588a85 100644 --- a/Kinvey/Kinvey/ObjectMapperSupport.swift +++ b/Kinvey/Kinvey/ObjectMapperSupport.swift @@ -30,22 +30,46 @@ public typealias BaseMappable = ObjectMapper.BaseMappable @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") public typealias MapContext = ObjectMapper.MapContext +// Workaround regression introduced with https://github.com/tristanhimmelman/ObjectMapper/pull/1079 +// These extension methods have been moved to Mappable which breaks our code because we need to call +// them on Mappable instances (e.g. StaticMappable doesn't conform to Mappable, but only to BaseMappable) +public extension BaseMappable { + + /// Initializes object from a JSON String + init?(JSONString: String, context: MapContext? = nil) { + if let obj: Self = Mapper(context: context).map(JSONString: JSONString) { + self = obj + } else { + return nil + } + } + + /// Initializes object from a JSON Dictionary + init?(JSON: [String: Any], context: MapContext? = nil) { + if let obj: Self = Mapper(context: context).map(JSON: JSON) { + self = obj + } else { + return nil + } + } +} + @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") extension Map { - + @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") public subscript(key: Key) -> Map where Key.RawValue == String { return self[key.rawValue] } - + } extension JSONDecodable where Self: BaseMappable { - + public mutating func refreshMappable(from json: [String : Any]) throws { mapping(map: ObjectMapper.Map(mappingType: .fromJSON, JSON: json)) } - + public static func decodeMappable(from data: Data) throws -> Self { guard let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String : Any] else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Data can't be converted to a Dictionary")) @@ -55,29 +79,29 @@ extension JSONDecodable where Self: BaseMappable { } return _self } - + public static func decodeMappableArray(from data: Data) throws -> [Any] { guard let jsonObjectArray = try JSONSerialization.jsonObject(with: data) as? [[String : Any]] else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Data can't be converted to a Array of Dictionaries")) } return [Self](JSONArray: jsonObjectArray) } - + public static func decodeMappable(from dictionary: [String : Any]) throws -> Self { guard let _self = Self(JSON: dictionary) else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Data can't be converted to \(Self.self)")) } return _self } - + } extension JSONEncodable where Self: BaseMappable { - + func encodeMappable() throws -> [String : Any] { return self.toJSON() } - + } /// Override operator used during the `propertyMapping(_:)` method. @@ -270,12 +294,12 @@ public func <- (left: List, right: (String, Map)) { @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") extension NSPredicate: StaticMappable { - + @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") public static func objectForMapping(map: Map) -> BaseMappable? { return nil } - + @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") public func mapping(map: Map) { if let json = mongoDBQuery { @@ -284,14 +308,14 @@ extension NSPredicate: StaticMappable { } } } - + } @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") class GeoPointTransform: TransformOf { - + static let shared = GeoPointTransform() - + init() { super.init(fromJSON: { (array) -> GeoPoint? in if let array = array, array.count == 2 { @@ -305,12 +329,12 @@ class GeoPointTransform: TransformOf { return nil }) } - + } @available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead") class ListValueTransform: TransformOf, [JsonDictionary]> where T: BaseMappable { - + init(_ list: List) { super.init(fromJSON: { (array) -> List? in if let array = array { @@ -330,7 +354,7 @@ class ListValueTransform: TransformOf, [JsonDictio return nil }) } - + } // MARK: String Value Transform @@ -449,10 +473,10 @@ class BoolValueTransform: TransformOf, [Bool]> { } class AclTransformType { - + typealias Object = [String] typealias JSON = String - + func transformFromJSON(_ value: Any?) -> [String]? { if let value = value as? String, let data = value.data(using: String.Encoding.utf8), @@ -463,7 +487,7 @@ class AclTransformType { } return nil } - + func transformToJSON(_ value: [String]?) -> String? { if let value = value, let data = try? JSONSerialization.data(withJSONObject: value), @@ -473,25 +497,25 @@ class AclTransformType { } return nil } - + } struct AnyTransform: ObjectMapper.TransformType { - + private let _transformFromJSON: (Any?) -> Any? private let _transformToJSON: (Any?) -> Any? - + init(_ transform: Transform) { _transformFromJSON = { transform.transformFromJSON($0) } _transformToJSON = { transform.transformToJSON($0 as? Transform.Object) } } - + func transformFromJSON(_ value: Any?) -> Any? { return _transformFromJSON(value) } - + func transformToJSON(_ value: Any?) -> Any? { return _transformToJSON(value) } - + } diff --git a/Kinvey/KinveyTests/CacheMigrationTestCaseStep2.swift b/Kinvey/KinveyTests/CacheMigrationTestCaseStep2.swift index bcc2da4c7..b4a05f25a 100644 --- a/Kinvey/KinveyTests/CacheMigrationTestCaseStep2.swift +++ b/Kinvey/KinveyTests/CacheMigrationTestCaseStep2.swift @@ -51,8 +51,8 @@ class CacheMigrationTestCaseStep2: XCTestCase { let zip2DataPath = Bundle(for: CacheMigrationTestCaseStep2.self).url(forResource: "CacheMigrationTestCaseData2", withExtension: "zip")! var destination = Realm.Configuration.defaultConfiguration.fileURL!.deletingLastPathComponent() #if os(macOS) - if let xcTestConfigurationFilePath = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] { - destination = URL(fileURLWithPath: xcTestConfigurationFilePath).deletingLastPathComponent() + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + destination = URL(fileURLWithPath: cacheBasePath) } #endif removeItemIfExists(at: destination.appendingPathComponent("__MACOSX")) @@ -79,8 +79,8 @@ class CacheMigrationTestCaseStep2: XCTestCase { if let fileURL = realmConfiguration.fileURL { var fileURL = fileURL #if os(macOS) - if let xcTestConfigurationFilePath = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] { - fileURL = URL(fileURLWithPath: xcTestConfigurationFilePath) + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + fileURL = URL(fileURLWithPath: cacheBasePath).appendingPathComponent("todel") } #endif fileURL = fileURL.deletingLastPathComponent() @@ -242,8 +242,8 @@ class CacheMigrationTestCaseStep2: XCTestCase { var realmConfiguration = Realm.Configuration.defaultConfiguration let lastPathComponent = realmConfiguration.fileURL!.lastPathComponent #if os(macOS) - if let xcTestConfigurationFilePath = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] { - realmConfiguration.fileURL = URL(fileURLWithPath: xcTestConfigurationFilePath) + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + realmConfiguration.fileURL = URL(fileURLWithPath: cacheBasePath).appendingPathComponent("todel") } #endif realmConfiguration.fileURL!.deleteLastPathComponent() diff --git a/Kinvey/KinveyTests/FileTestCase.swift b/Kinvey/KinveyTests/FileTestCase.swift index 547f5e390..d53b80c09 100644 --- a/Kinvey/KinveyTests/FileTestCase.swift +++ b/Kinvey/KinveyTests/FileTestCase.swift @@ -16,12 +16,12 @@ import Nimble typealias Image = UIImage #endif -class FileTestCase: StoreTestCase { +class FileTestCase: StoreTestCase { let caminandes3TrailerURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Caminandes 3 - TRAILER.mp4") let caminandes3TrailerImageURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Caminandes 3 - TRAILER.jpg") var file: File? - var myFile: MyFile? + var myFile: FileType? var caminandes3TrailerFileSize: UInt64 { return try! FileManager.default.attributesOfItem(atPath: caminandes3TrailerURL.path).filter { $0.key == .size }.first!.value as! UInt64 } @@ -327,9 +327,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: return HttpResponse(error: timeoutError) @@ -386,9 +384,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: return HttpResponse(error: timeoutError) @@ -447,9 +443,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: return HttpResponse(statusCode: 404, data: nil) @@ -507,9 +501,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: return HttpResponse(error: timeoutError) @@ -568,9 +560,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: return HttpResponse(error: timeoutError) @@ -610,13 +600,13 @@ class FileTestCase: StoreTestCase { client.logNetworkEnabled = originalLogNetworkEnabled } - var file = MyFile() + var file = FileType() file.label = "trailer" file.publicAccessible = true self.file = file let path = caminandes3TrailerURL.path - let fileStore = FileStore() + let fileStore = FileStore() var uploadProgressCount = 0 @@ -649,9 +639,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: if let stream = request.httpBodyStream { @@ -759,7 +747,15 @@ class FileTestCase: StoreTestCase { XCTAssertNotNil(file) XCTAssertNil(error) - + XCTAssertNotNil(uploadedFile) + + defer { + expectationUpload?.fulfill() + } + + guard uploadedFile != nil else { + return + } file = uploadedFile! XCTAssertNotNil(file.path) @@ -772,8 +768,6 @@ class FileTestCase: StoreTestCase { let diff = memoryNow - memoryBefore XCTAssertLessThan(diff, 15) //15 MB } - - expectationUpload?.fulfill() } keyValueObservingExpectation(for: request.progress, keyPath: #selector(getter: request.progress.fractionCompleted).description) { (object, info) -> Bool in @@ -881,9 +875,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: if let stream = request.httpBodyStream { @@ -1110,9 +1102,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: if let stream = request.httpBodyStream { @@ -1347,9 +1337,7 @@ class FileTestCase: StoreTestCase { "ect": "2016-12-03T08:52:19.204Z" ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=2a37d253-752f-42cd-987e-db319a626077%2Fa2f88ffc-e7fe-4d17-aa69-063088cb24fa&uploadType=resumable&predefinedAcl=publicRead&upload_id=AEnB2Uqwlm2GQ0JWMApi0ApeBHQ0PxjY3hSe_VNs5geuZFxLBkrwiI0gLldrE8GgkqX4ahWtRJ1MHombFq8hQc9o5772htAvDQ", - "_expiresAt": "2016-12-10T08:52:19.488Z", - "_requiredHeaders": [ - ] + "_expiresAt": "2016-12-10T08:52:19.488Z" ]) case 1: if let stream = request.httpBodyStream { @@ -1635,13 +1623,13 @@ class FileTestCase: StoreTestCase { func testDownloadAndResume() { signUp() - let file = MyFile() + let file = FileType() file.label = "trailer" file.publicAccessible = true self.file = file let path = caminandes3TrailerURL.path - let fileStore = FileStore() + let fileStore = FileStore() do { if useMockData { @@ -1664,9 +1652,7 @@ class FileTestCase: StoreTestCase { "ect": Date().toISO8601() ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/\(UUID().uuidString)/o?name=\(UUID().uuidString)%2F\(UUID().uuidString)&uploadType=resumable&predefinedAcl=publicRead&upload_id=\(UUID().uuidString)", - "_expiresAt": Date().toISO8601(), - "_requiredHeaders": [ - ] + "_expiresAt": Date().toISO8601() ]) case 1: return HttpResponse(json: [ @@ -1789,9 +1775,13 @@ class FileTestCase: StoreTestCase { } } + guard self.myFile != nil else { + return + } + do { weak var expectationDownload = expectation(description: "Download") - + let request = fileStore.download(self.myFile!) { (file, data: Data?, error) in self.myFile = file XCTFail(error?.localizedDescription ?? "Handler was not expected to be called") @@ -2317,9 +2307,7 @@ class FileTestCase: StoreTestCase { "ect": Date().toISO8601() ], "_uploadURL": "https://www.googleapis.com/upload/storage/v1/b/\(UUID().uuidString)/o?name=\(UUID().uuidString)%2F\(UUID().uuidString)&uploadType=resumable&predefinedAcl=publicRead&upload_id=\(UUID().uuidString)", - "_expiresAt": Date().toISO8601(), - "_requiredHeaders": [ - ] + "_expiresAt": Date().toISO8601() ]) case 1: return HttpResponse(json: [ @@ -2561,9 +2549,7 @@ class FileTestCase: StoreTestCase { "ect" : Date().toISO8601() ], "_uploadURL" : "https://www.googleapis.com/upload/storage/v1/b/0b5b1cd673164e3185a2e75e815f5cfe/o?name=b69d8159-e59f-4337-b03b-1c2df3ccfed9%2F9b77d4f0-43ea-4d70-9f65-e40808d1e429&uploadType=resumable&upload_id=AEnB2Up1heH7qbZXQIqsaT-XJNJTv3OKufiMN_9OXh5qGPVfwP4SaWrU5LW7-ZXswXc11l_Wi027IUjZx44CzajfycP8aam7HQ", - "_expiresAt" : Date(timeIntervalSinceNow: 7 * TimeUnit.day.timeInterval).toISO8601(), - "_requiredHeaders" : [ - ] + "_expiresAt" : Date(timeIntervalSinceNow: 7 * TimeUnit.day.timeInterval).toISO8601() ]) case 1: return HttpResponse(json: [ @@ -3131,9 +3117,7 @@ class FileTestCase: StoreTestCase { "ect" : Date().toISO8601() ], "_uploadURL" : "https://www.googleapis.com/upload/storage/v1/b/\(UUID().uuidString)", - "_expiresAt" : Date().toISO8601(), - "_requiredHeaders" : [ - ] + "_expiresAt" : Date().toISO8601() ] ) default: @@ -3211,9 +3195,7 @@ class FileTestCase: StoreTestCase { "ect" : Date().toISO8601() ], "_uploadURL" : "https://www.googleapis.com/upload/storage/v1/b/\(UUID().uuidString)", - "_expiresAt" : Date().toISO8601(), - "_requiredHeaders" : [ - ] + "_expiresAt" : Date().toISO8601() ] ) default: @@ -3290,7 +3272,8 @@ class FileTestCase: StoreTestCase { ], "_uploadURL" : "https://www.googleapis.com/upload/storage/v1/b/\(UUID().uuidString)", "_expiresAt" : Date().toISO8601(), - "_requiredHeaders" : [ + "_requiredHeaders": [ + "TEST_HEADER": "TEST_HEADER_VALUE" ] ] ) @@ -3311,6 +3294,8 @@ class FileTestCase: StoreTestCase { XCTAssertNotNil(file.fileId) XCTAssertNotNil(file.uploadURL) XCTAssertFalse(file.publicAccessible) + XCTAssertNotNil(file.uploadHeaders) + XCTAssertEqual(file.uploadHeaders!["TEST_HEADER"], "TEST_HEADER_VALUE") } } @@ -3334,5 +3319,99 @@ class FileTestCase: StoreTestCase { XCTAssertTimeoutError(error) } } - +} + +class MyFileTestCase : FileTestCase { + func testInvalidBodyIgnored() { + mockResponse { request in + switch (request.url?.path, request.url?.query) { + case ("/blob/_kid_"?, "tls=true"?): + return HttpResponse( + statusCode: 201, + json: [ + "_id" : UUID().uuidString, + "_requiredHeaders": "wrongString" + ] + ) + default: + XCTFail(request.url?.absoluteString ?? "nil") + return HttpResponse(statusCode: 404, data: Data()) + } + } + defer { + setURLProtocol(nil) + } + + let inputStream = InputStream(data: "test".data(using: .utf8)!) + defer { + inputStream.close() + } + let fileStore = FileStore() + let request = fileStore.create(MyFile(), stream: inputStream) + let file = try? request.waitForResult().value() + XCTAssertNotNil(file) + if let file = file { + XCTAssertNotNil(file.fileId) + XCTAssertNil(file.uploadHeaders) + } + } +} + +class MyFileCodableTestCase : FileTestCase { + func testInvalidBodyThrows() { + mockResponse { request in + switch (request.url?.path, request.url?.query) { + case ("/blob/_kid_"?, "tls=true"?): + return HttpResponse( + statusCode: 201, + json: [ + "_requiredHeaders": "wrongString" + ] + ) + default: + XCTFail(request.url?.absoluteString ?? "nil") + return HttpResponse(statusCode: 404, data: Data()) + } + } + defer { + setURLProtocol(nil) + } + + let inputStream = InputStream(data: "test".data(using: .utf8)!) + defer { + inputStream.close() + } + let fileStore = FileStore() + let request = fileStore.create(MyFileCodable(), stream: inputStream) + XCTAssertThrowsError(try request.waitForResult().value()) { + let error = $0 + XCTAssertEqual(error.localizedDescription, "The data couldn’t be read because it isn’t in the correct format.") + } + } +} + +class MyFileTestCaseRunner: XCTestCase { + override func run() { + let suite = XCTestSuite(forTestCaseClass: MyFileTestCase.self) + suite.run() + super.run() + } + + // At least one func is needed for `run` to be called + func testDummy() { + XCTAssert(true) + } +} + +class MyFileCodableTestCaseRunner: XCTestCase { + override func run() { + let suite = XCTestSuite(forTestCaseClass: MyFileCodableTestCase.self) + suite.run() + super.run() + } + + // At least one func is needed for `run` to be called + func testDummy() { + XCTAssert(true) + } } diff --git a/Kinvey/KinveyTests/MyFile.swift b/Kinvey/KinveyTests/MyFile.swift index b6c84ceb4..2f9915975 100644 --- a/Kinvey/KinveyTests/MyFile.swift +++ b/Kinvey/KinveyTests/MyFile.swift @@ -9,19 +9,55 @@ import Foundation import Kinvey -class MyFile: File { - +@objc protocol MyFileProtocol { @objc - dynamic var label: String? - - public convenience required init?(map: Map) { - self.init() + dynamic var label: String? { get set } +} + +class MyFile: File, MyFileProtocol { + + enum MyCodingKeys: String, CodingKey { + case label } - + + var label: String? + override func mapping(map: Map) { super.mapping(map: map) - + label <- ("label", map["label"]) } + +} + +class MyFileCodable : File, MyFileProtocol, Codable { + var label: String? + enum MyCodingKeys: String, CodingKey { + case label + } + + public required init() { + super.init() + } + + required init(realm: RLMRealm, schema: RLMObjectSchema) { + super.init(realm: realm, schema: schema) + } + + required init(value: Any, schema: RLMSchema) { + super.init(value: value, schema: schema) + } + + public required override init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: MyCodingKeys.self) + label = try container.decodeIfPresent(String.self, forKey: .label) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: MyCodingKeys.self) + try container.encodeIfPresent(label, forKey: .label) + } } diff --git a/Makefile b/Makefile index 1fee0b328..5cc5aaf22 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ DEVCENTER_GIT=git@github.com:Kinvey/devcenter.git DEVCENTER_GIT_TEST=https://git.heroku.com/v3yk1n-devcenter.git DEVCENTER_GIT_PROD=https://git.heroku.com/kinvey-devcenter-prod.git CARTFILE_RESOLVED_MD5=$(shell { cat Cartfile.resolved; swift --version | sed -e "s/Apple //" | head -1 | awk '{ print "Swift " $$3 }'; } | tr "\n" "\n" | md5) -DESTINATION_OS?=13.0 +DESTINATION_OS?=13.4 DESTINATION_NAME?=iPhone 11 Pro ECHO?=no @@ -22,7 +22,7 @@ clean: echo: @echo $(ECHO) - + checkout-dependencies: carthage checkout @@ -38,7 +38,7 @@ build-dependencies-ios: checkout-dependencies cartfile-md5: @echo $(CARTFILE_RESOLVED_MD5) - + cache: test -s Carthage/$(CARTFILE_RESOLVED_MD5).tar.lzma || \ { \ @@ -75,7 +75,7 @@ archive-ios: test: test-ios test-macos - + test-ios: xcodebuild -workspace Kinvey.xcworkspace -scheme Kinvey -destination 'OS=$(DESTINATION_OS),name=$(DESTINATION_NAME)' test -enableCodeCoverage YES @@ -99,7 +99,7 @@ docs: --xcodebuild-arguments -workspace,Kinvey.xcworkspace,-scheme,Kinvey \ --module Kinvey \ --output docs - + deploy-cocoapods: pod trunk push Kinvey.podspec @@ -158,21 +158,21 @@ show-version: @/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "${PWD}/Kinvey/Kinvey/Info.plist" | xargs echo 'Info.plist ' @cat Kinvey.podspec | grep "s.version\s*=\s*\"[0-9]*.[0-9]*.[0-9]*\"" | awk {'print $$3'} | sed 's/"//g' | xargs echo 'Kinvey.podspec' @agvtool what-version | awk '0 == NR % 2' | awk {'print $1'} | xargs echo 'Project Version ' - + set-version: @echo 'Current Version:' @echo '----------------------' @$(MAKE) show-version - + @echo - + @echo 'New Version:' @read version; \ \ /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $$version" "${PWD}/Kinvey/Kinvey/Info.plist"; \ sed -i -e "s/s.version[ ]*=[ ]*\"[0-9]*.[0-9]*.[0-9]*\"/s.version = \"$$version\"/g" Kinvey.podspec; \ rm Kinvey.podspec-e - + @echo @echo diff --git a/Package.swift b/Package.swift index ddcc597ce..6179cd2c8 100644 --- a/Package.swift +++ b/Package.swift @@ -55,7 +55,8 @@ let package = Package( ), .package( url: "https://github.com/Quick/Nimble.git", - .branch("master") + // In master the package now requires Xcode 11.4 (Swift 5.2) which we still don't have on CI + .upToNextMinor(from: "8.0.7") ), .package( url: "https://github.com/weichsel/ZIPFoundation.git",