diff --git a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/CommonRunTimeError+PushNotificationsErrorConvertible.swift b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/CommonRunTimeError+PushNotificationsErrorConvertible.swift index c74f540617..0c07856389 100644 --- a/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/CommonRunTimeError+PushNotificationsErrorConvertible.swift +++ b/AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Error/CommonRunTimeError+PushNotificationsErrorConvertible.swift @@ -12,12 +12,17 @@ import AwsCommonRuntimeKit extension CommonRunTimeError: PushNotificationsErrorConvertible { var pushNotificationsError: PushNotificationsError { + if isConnectivityError { + return .network( + PushNotificationsPluginErrorConstants.deviceOffline.errorDescription, + PushNotificationsPluginErrorConstants.deviceOffline.recoverySuggestion, + self + ) + } + switch self { case .crtError(let crtError): - let errorDescription = isConnectivityError - ? AWSPinpointErrorConstants.deviceOffline.errorDescription - : crtError.message - return .unknown(errorDescription, self) + return .unknown(crtError.message, self) } } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift index 48e428c425..cbfd7ee52e 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift @@ -91,22 +91,25 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation< do { let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key - let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata) let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if request.data.count > StorageUploadDataRequest.Options.multiPartUploadSizeThreshold { - storageService.multiPartUpload(serviceKey: serviceKey, - uploadSource: .data(request.data), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.multiPartUpload( + serviceKey: serviceKey, + uploadSource: .data(request.data), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } else { - storageService.upload(serviceKey: serviceKey, - uploadSource: .data(request.data), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.upload( + serviceKey: serviceKey, + uploadSource: .data(request.data), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift index fce5fdd94d..617602388a 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift @@ -115,22 +115,25 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation< do { let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId) let serviceKey = prefix + request.key - let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata) let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions) if uploadSize > StorageUploadFileRequest.Options.multiPartUploadSizeThreshold { - storageService.multiPartUpload(serviceKey: serviceKey, - uploadSource: .local(request.local), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.multiPartUpload( + serviceKey: serviceKey, + uploadSource: .local(request.local), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } else { - storageService.upload(serviceKey: serviceKey, - uploadSource: .local(request.local), - contentType: request.options.contentType, - metadata: serviceMetadata, - accelerate: accelerate) { [weak self] event in + storageService.upload( + serviceKey: serviceKey, + uploadSource: .local(request.local), + contentType: request.options.contentType, + metadata: request.options.metadata, + accelerate: accelerate + ) { [weak self] event in self?.onServiceEvent(event: event) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift index e25f486266..7884e13482 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadClient.swift @@ -75,7 +75,19 @@ class DefaultStorageMultipartUploadClient: StorageMultipartUploadClient { // The AWS S3 SDK handles the request so there will be not taskIdentifier session.handle(multipartUploadEvent: .creating) - let request = CreateMultipartUploadRequest(bucket: bucket, key: key) + // User-defined metadata needs to provided + // when initiating the MPU. + // -- + // https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html#mpu-process + // > Multipart upload initiation + // "If you want to provide any metadata describing the object + // being uploaded, you must provide it in the request to initiate + // multipart upload." + let request = CreateMultipartUploadRequest( + bucket: bucket, + key: key, + metadata: metadata + ) serviceProxy.awsS3.createMultipartUpload(request) { [weak self] result in guard let self = self else { return } switch result { @@ -139,7 +151,9 @@ class DefaultStorageMultipartUploadClient: StorageMultipartUploadClient { let preSignedURL = try await serviceProxy.preSignedURLBuilder.getPreSignedURL( key: self.key, signingOperation: operation, - metadata: self.metadata, + // user-controlled metadata should *not* be provided + // with each upload part. + metadata: nil, accelerate: nil, expires: nil ) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift index e37a03c4c0..ac610b39fb 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Utils/StorageRequestUtils+Getter.swift @@ -25,19 +25,6 @@ extension StorageRequestUtils { return accessLevel.serviceAccessPrefix + "/" } - static func getServiceMetadata(_ metadata: [String: String]?) -> [String: String]? { - guard let metadata = metadata else { - return nil - } - var serviceMetadata: [String: String] = [:] - for (key, value) in metadata { - let serviceKey = metadataKeyPrefix + key - serviceMetadata[serviceKey] = value - } - - return serviceMetadata - } - static func getSize(_ file: URL) throws -> UInt64 { if let error = validateFileExists(file) { throw StorageError.localFileNotFound(error.errorDescription, error.recoverySuggestion) diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift index 208f97fafa..2abd07f580 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StoragePutDataOperationTests.swift @@ -84,7 +84,6 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { let expectedUploadSource = UploadSource.data(testData) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadDataRequest.Options(accessLevel: .protected, metadata: metadata, @@ -119,7 +118,7 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } func testUploadDataOperationUploadFail() { @@ -183,7 +182,6 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { "Could not create data object greater than MultiPartUploadSizeThreshold") let expectedUploadSource = UploadSource.data(testLargeData) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadDataRequest.Options(accessLevel: .protected, metadata: metadata, @@ -218,7 +216,7 @@ class AWSS3StorageUploadDataOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } // TODO: test pause, resume, canel, etc. diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift index 1c1b904312..52800e958e 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Operation/AWSS3StorageUploadFileOperationTests.swift @@ -118,7 +118,6 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { FileManager.default.createFile(atPath: filePath, contents: testData, attributes: nil) let expectedUploadSource = UploadSource.local(fileURL) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadFileRequest.Options(accessLevel: .protected, metadata: metadata, @@ -153,7 +152,7 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } func testUploadFileOperationUploadFail() { @@ -219,7 +218,6 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { "Could not create data object greater than MultiPartUploadSizeThreshold") let expectedUploadSource = UploadSource.local(testURL) let metadata = ["mykey": "Value"] - let expectedMetadata = ["x-amz-meta-mykey": "Value"] let options = StorageUploadFileRequest.Options(accessLevel: .protected, metadata: metadata, @@ -254,7 +252,7 @@ class AWSS3StorageUploadFileOperationTests: AWSS3StorageOperationTestBase { key: testKey, uploadSource: expectedUploadSource, contentType: testContentType, - metadata: expectedMetadata) + metadata: metadata) } // TODO: test pause, resume, canel, etc. diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift index c70f56e7b4..295ffbd3f0 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Utils/StorageRequestUtilsGetterTests.swift @@ -67,20 +67,6 @@ class StorageRequestUtilsGetterTests: XCTestCase { XCTAssertEqual(result, expected) } - // MARK: GetServiceMetadata tests - - func testGetServiceMetadataConstructsMetadataKeysWithS3Prefix() { - let metadata = ["key1": "value1", "key2": "value2"] - let results = StorageRequestUtils.getServiceMetadata(metadata) - XCTAssertNotNil(results) - - for (key, value) in results! { - XCTAssertNotNil(key) - XCTAssertNotNil(value) - XCTAssertTrue(key.contains(StorageRequestUtils.metadataKeyPrefix)) - } - } - // MARK: GetSize tests func testGetSizeForFileUploadSourceReturnsSize() throws { diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginUploadMetadataTestCase.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginUploadMetadataTestCase.swift new file mode 100644 index 0000000000..9d729d496c --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/AWSS3StoragePluginUploadMetadataTestCase.swift @@ -0,0 +1,253 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import Amplify +import AWSPluginsCore +import AWSS3StoragePlugin +import AWSS3 + +class AWSS3StoragePluginUploadMetadataTestCase: AWSS3StoragePluginTestBase { + // MARK: - Tests + + /// Given: `StorageUploadFileRequest.Options` with `metadata` + /// When: Uploading a file below the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadSmallFileWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-small-file-with-metadata", UUID().uuidString) + let options = StorageUploadFileRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + let fileURL = temporaryFile(named: key, data: data(mb: 1)) + _ = try await Amplify.Storage.uploadFile( + key: key, + local: fileURL, + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadFileRequest.Options` with `metadata` + /// When: Uploading a file above the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadLargeFileWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-large-file-with-metadata", UUID().uuidString) + let options = StorageUploadFileRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + let fileURL = temporaryFile(named: key, data: data(mb: 7)) + _ = try await Amplify.Storage.uploadFile( + key: key, + local: fileURL, + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadDataRequest.Options` with `metadata` + /// When: Uploading data with a size below the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadSmallDataWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-small-data-with-metadata", UUID().uuidString) + let options = StorageUploadDataRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + _ = try await Amplify.Storage.uploadData( + key: key, + data: data(mb: 1), + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadDataRequest.Options` with `metadata` + /// When: Uploading data with a size below the MPU threshold. + /// Then: That object's headers (retrieved via `HeadObject`) should contain the passed`metadata` + func test_uploadLargeDataWithMetadata_headContainsMetadata() async throws { + // Include metadata in upload file request + let (mdKey, mdValue) = ("upload-large-data-with-metadata", UUID().uuidString) + let options = StorageUploadDataRequest.Options( + metadata: [mdKey: mdValue] + ) + + // upload file + let key = UUID().uuidString + _ = try await Amplify.Storage.uploadData( + key: key, + data: data(mb: 7), + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata?[mdKey], + mdValue, + """ + Expected `headObject().metadata` to contain key-value + pair - \(mdKey): \(mdKey) + Instead, received metadata is \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + /// Given: `StorageUploadDataRequest.Options` with multiple + /// `metadata` key-value pairs. + /// When: Calling uploading an object via `uploadData` + /// Then: That object's headers (retrieved via `HeadObject`) should contain + /// all key-value pairs`metadata` + func test_uploadWithMultipleMetadataPairs() async throws { + // Include metadata in upload file request + let range = (1...11) + let metadata = zip(range, range.dropFirst()) + .map { tuple -> (String, String) in + (.init(tuple.0), .init(tuple.0)) + } + .reduce(into: [String: String]()) { dict, pair in + let (key, value) = pair + dict[key] = value + } + + let options = StorageUploadDataRequest.Options( + metadata: metadata + ) + + // upload file + let key = UUID().uuidString + _ = try await Amplify.Storage.uploadData( + key: key, + data: data(mb: 1), + options: options + ).value + + // call `HeadObject` through SDK escape hatch + let head = try await headObject(key: "public/\(key)") + + // the `HeadObject` response should contain metadata + // with the key-value pair including in the upload + XCTAssertEqual( + head.metadata, + metadata, + """ + Expected `headObject().metadata` to equal + user-defined metadata \(metadata). + Instead, received metadata: \(head.metadata as Any) + """ + ) + + // clean up + _ = try await Amplify.Storage.remove(key: key) + } + + // MARK: - Helper Functions + private func data(mb: Int) -> Data { + Data( + repeating: 0xff, + count: 1_024 * 1_024 * mb + ) + } + + private func temporaryFile(named key: String, data: Data) -> URL { + let filePath = "\(NSTemporaryDirectory() + key).tmp" + let fileURL = URL(fileURLWithPath: filePath) + FileManager.default.createFile( + atPath: filePath, + contents: data, + attributes: nil + ) + return fileURL + } + + private func headObject(key: String) async throws -> HeadObjectOutputResponse { + let plugin = try Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") + let storagePlugin = try XCTUnwrap( + plugin as? AWSS3StoragePlugin, + "Cast to `AWSS3StoragePlugin` failed" + ) + let s3Client = storagePlugin.getEscapeHatch() + let bucket = try AWSS3StoragePluginTestBase.getBucketFromConfig( + forResource: "amplifyconfiguration" + ) + let input = HeadObjectInput( + bucket: bucket, + key: key + ) + + return try await s3Client.headObject(input: input) + } +} + + diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift index b471c1cd5b..5131743d06 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/AWSS3StoragePluginIntegrationTests/Helpers/TestConfigHelper.swift @@ -29,7 +29,7 @@ class TestConfigHelper { } static func retrieve(forResource: String) throws -> Data { - guard let path = Bundle(for: self).path(forResource: forResource, ofType: "json") else { + guard let path = Bundle.main.path(forResource: forResource, ofType: "json") else { throw "Could not retrieve configuration file: \(forResource)" } diff --git a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj index 944c6db9d6..d6c2a2c236 100644 --- a/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 56043E9329FC4D33003E3424 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = D5C0382101A0E23943FDF4CB /* amplifyconfiguration.json */; }; 562B9AA42A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */; }; 562B9AA52A0D734E00A96FC6 /* AWSS3StoragePluginRequestRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */; }; + 565DF1702953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */; }; 681D7D4E2A4263C200F7C310 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB06F28BEAF1500C8A6EB /* ContentView.swift */; }; 681D7D4F2A4263C200F7C310 /* StorageHostAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB06D28BEAF1500C8A6EB /* StorageHostAppApp.swift */; }; 681D7D552A4263E500F7C310 /* AuthSignInHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB0C228BEB45600C8A6EB /* AuthSignInHelper.swift */; }; @@ -39,7 +40,6 @@ 681D7D792A4264D200F7C310 /* AWSCognitoAuthPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 681D7D782A4264D200F7C310 /* AWSCognitoAuthPlugin */; }; 681D7D7B2A4264D200F7C310 /* AWSS3StoragePlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 681D7D7A2A4264D200F7C310 /* AWSS3StoragePlugin */; }; 681D7D852A426FF500F7C310 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = D5C0382101A0E23943FDF4CB /* amplifyconfiguration.json */; }; - 565DF1702953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */; }; 681DFEB228E748270000C36A /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEAF28E748270000C36A /* AsyncTesting.swift */; }; 681DFEB328E748270000C36A /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB028E748270000C36A /* AsyncExpectation.swift */; }; 681DFEB428E748270000C36A /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */; }; @@ -59,6 +59,7 @@ 68828E4628C2736C006E7C0A /* AWSS3StoragePluginProgressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08C28BEAF8E00C8A6EB /* AWSS3StoragePluginProgressTests.swift */; }; 68828E4728C27745006E7C0A /* AWSS3StoragePluginPutDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08828BEAF8E00C8A6EB /* AWSS3StoragePluginPutDataResumabilityTests.swift */; }; 68828E4828C2AAA6006E7C0A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08B28BEAF8E00C8A6EB /* AWSS3StoragePluginGetDataResumabilityTests.swift */; }; + 901AB3E92AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */; }; 97914BA32955798D002000EA /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEAF28E748270000C36A /* AsyncTesting.swift */; }; 97914BA52955798D002000EA /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB028E748270000C36A /* AsyncExpectation.swift */; }; 97914BB02955798D002000EA /* XCTestCase+AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */; }; @@ -101,10 +102,10 @@ 0311113828EBEEA700D58441 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; 031BC3F228EC9B2C0047B2E8 /* AppIcon.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = AppIcon.xcassets; sourceTree = ""; }; 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginRequestRecorder.swift; sourceTree = ""; }; + 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginAccelerateIntegrationTests.swift; sourceTree = ""; }; 681D7D392A42637700F7C310 /* StorageWatchApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StorageWatchApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 681D7D502A4263CA00F7C310 /* StorageWatchApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StorageWatchApp.entitlements; sourceTree = ""; }; 681D7D6C2A4263E500F7C310 /* AWSS3StoragePluginIntegrationTestsWatch.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSS3StoragePluginIntegrationTestsWatch.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 565DF16F2953BAEA000DCCF7 /* AWSS3StoragePluginAccelerateIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginAccelerateIntegrationTests.swift; sourceTree = ""; }; 681DFEAF28E748270000C36A /* AsyncTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncTesting.swift; sourceTree = ""; }; 681DFEB028E748270000C36A /* AsyncExpectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncExpectation.swift; sourceTree = ""; }; 681DFEB128E748270000C36A /* XCTestCase+AsyncTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+AsyncTesting.swift"; sourceTree = ""; }; @@ -127,6 +128,7 @@ 684FB0A928BEB07200C8A6EB /* AWSS3StoragePluginIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSS3StoragePluginIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 684FB0C228BEB45600C8A6EB /* AuthSignInHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSignInHelper.swift; sourceTree = ""; }; 684FB0C528BEB84800C8A6EB /* StorageHostApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StorageHostApp.entitlements; sourceTree = ""; }; + 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginUploadMetadataTestCase.swift; sourceTree = ""; }; 97914B972955797E002000EA /* StorageStressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageStressTests.swift; sourceTree = ""; }; 97914BB92955798D002000EA /* StorageStressTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StorageStressTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 97914BBA29557A52002000EA /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -265,6 +267,7 @@ 684FB08C28BEAF8E00C8A6EB /* AWSS3StoragePluginProgressTests.swift */, 684FB07E28BEAF8E00C8A6EB /* AWSS3StoragePluginTestBase.swift */, 562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */, + 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */, 684FB08728BEAF8E00C8A6EB /* ResumabilityTests */, ); path = AWSS3StoragePluginIntegrationTests; @@ -608,6 +611,7 @@ 684FB0C328BEB45600C8A6EB /* AuthSignInHelper.swift in Sources */, 681DFEB228E748270000C36A /* AsyncTesting.swift in Sources */, 68828E4828C2AAA6006E7C0A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */, + 901AB3E92AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift in Sources */, 681DFEB328E748270000C36A /* AsyncExpectation.swift in Sources */, 68828E4628C2736C006E7C0A /* AWSS3StoragePluginProgressTests.swift in Sources */, 684FB0B528BEB08900C8A6EB /* AWSS3StoragePluginAccessLevelTests.swift in Sources */,