Skip to content

Commit 9e2bc8b

Browse files
authored
chore(storage): add new upload and download integration tests (#3581)
1 parent b8ec8d8 commit 9e2bc8b

File tree

6 files changed

+286
-8
lines changed

6 files changed

+286
-8
lines changed

AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/AWSS3StoragePlugin+AsyncClientBehavior.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,7 @@ extension AWSS3StoragePlugin {
151151
options: StorageUploadDataOperation.Request.Options? = nil
152152
) -> StorageUploadDataTask {
153153
let options = options ?? StorageUploadDataRequest.Options()
154-
let path = "" //TODO: resolve path
155-
let request = StorageUploadDataRequest(key: path, data: data, options: options)
154+
let request = StorageUploadDataRequest(path: path, data: data, options: options)
156155
let operation = AWSS3StorageUploadDataOperation(request,
157156
storageConfiguration: storageConfiguration,
158157
storageService: storageService,
@@ -188,8 +187,7 @@ extension AWSS3StoragePlugin {
188187
options: StorageUploadFileOperation.Request.Options? = nil
189188
) -> StorageUploadFileTask {
190189
let options = options ?? StorageUploadFileRequest.Options()
191-
let path = "" //TODO: resolve path
192-
let request = StorageUploadFileRequest(key: path, local: local, options: options)
190+
let request = StorageUploadFileRequest(path: path, local: local, options: options)
193191
let operation = AWSS3StorageUploadFileOperation(request,
194192
storageConfiguration: storageConfiguration,
195193
storageService: storageService,

AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadDataOperation.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation<
2626
let authService: AWSAuthServiceBehavior
2727

2828
var storageTaskReference: StorageTaskReference?
29-
29+
private var resolvedPath: String?
3030
/// Serial queue for synchronizing access to `storageTaskReference`.
3131
private let storageTaskActionQueue = DispatchQueue(label: "com.amazonaws.amplify.StorageTaskActionQueue")
3232

@@ -92,6 +92,7 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation<
9292
let serviceKey: String
9393
if let path = request.path {
9494
serviceKey = try await path.resolvePath(authService: self.authService)
95+
resolvedPath = serviceKey
9596
} else {
9697
let prefixResolver = storageConfiguration.prefixResolver ??
9798
StorageAccessLevelAwarePrefixResolver(authService: authService)
@@ -142,7 +143,11 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation<
142143
case .inProcess(let progress):
143144
dispatch(progress)
144145
case .completed:
145-
dispatch(request.key)
146+
if let path = resolvedPath {
147+
dispatch(path)
148+
} else {
149+
dispatch(request.key)
150+
}
146151
finish()
147152
case .failed(let error):
148153
dispatch(error)

AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Operation/AWSS3StorageUploadFileOperation.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation<
2626
let authService: AWSAuthServiceBehavior
2727

2828
var storageTaskReference: StorageTaskReference?
29-
29+
private var resolvedPath: String?
3030
/// Serial queue for synchronizing access to `storageTaskReference`.
3131
private let storageTaskActionQueue = DispatchQueue(label: "com.amazonaws.amplify.StorageTaskActionQueue")
3232

@@ -114,6 +114,7 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation<
114114
let serviceKey: String
115115
if let path = request.path {
116116
serviceKey = try await path.resolvePath(authService: self.authService)
117+
resolvedPath = serviceKey
117118
} else {
118119
let prefixResolver = storageConfiguration.prefixResolver ??
119120
StorageAccessLevelAwarePrefixResolver(authService: authService)
@@ -165,7 +166,11 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation<
165166
case .inProcess(let progress):
166167
dispatch(progress)
167168
case .completed:
168-
dispatch(request.key)
169+
if let path = resolvedPath {
170+
dispatch(path)
171+
} else {
172+
dispatch(request.key)
173+
}
169174
finish()
170175
case .failed(let error):
171176
dispatch(error)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
@testable import Amplify
9+
10+
import AWSS3StoragePlugin
11+
import ClientRuntime
12+
import CryptoKit
13+
import XCTest
14+
15+
class AWSS3StoragePluginDownloadIntegrationTests: AWSS3StoragePluginTestBase {
16+
/// Given: An object in storage
17+
/// When: Call the downloadData API
18+
/// Then: The operation completes successfully with the data retrieved
19+
func testDownloadDataToMemory() async throws {
20+
let key = UUID().uuidString
21+
try await uploadData(key: key, data: Data(key.utf8))
22+
_ = try await Amplify.Storage.downloadData(path: .fromString("public/\(key)"), options: .init()).value
23+
_ = try await Amplify.Storage.remove(path: .fromString("public/\(key)"))
24+
}
25+
/// Given: An object in storage
26+
/// When: Call the downloadFile API
27+
/// Then: The operation completes successfully the local file containing the data from the object
28+
func testDownloadFile() async throws {
29+
let key = UUID().uuidString
30+
let timestamp = String(Date().timeIntervalSince1970)
31+
let timestampData = Data(timestamp.utf8)
32+
try await uploadData(key: key, data: timestampData)
33+
let filePath = NSTemporaryDirectory() + key + ".tmp"
34+
let fileURL = URL(fileURLWithPath: filePath)
35+
removeIfExists(fileURL)
36+
37+
_ = try await Amplify.Storage.downloadFile(path: .fromString("public/\(key)"), local: fileURL, options: .init()).value
38+
39+
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
40+
XCTAssertTrue(fileExists)
41+
do {
42+
let result = try String(contentsOf: fileURL, encoding: .utf8)
43+
XCTAssertEqual(result, timestamp)
44+
} catch {
45+
XCTFail("Failed to read file that has been downloaded to")
46+
}
47+
removeIfExists(fileURL)
48+
_ = try await Amplify.Storage.remove(key: key)
49+
}
50+
51+
func removeIfExists(_ fileURL: URL) {
52+
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
53+
if fileExists {
54+
do {
55+
try FileManager.default.removeItem(at: fileURL)
56+
} catch {
57+
XCTFail("Failed to delete file at \(fileURL)")
58+
}
59+
}
60+
}
61+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
@testable import Amplify
9+
10+
import AWSS3StoragePlugin
11+
import ClientRuntime
12+
import CryptoKit
13+
import XCTest
14+
15+
class AWSS3StoragePluginUploadIntegrationTests: AWSS3StoragePluginTestBase {
16+
17+
var uploadedKeys: [String]!
18+
19+
/// Represents expected pieces of the User-Agent header of an SDK http request.
20+
///
21+
/// Example SDK User-Agent:
22+
/// ```
23+
/// User-Agent: aws-sdk-swift/1.0 api/s3/1.0 os/iOS/16.4.0 lang/swift/5.8
24+
/// ```
25+
/// - Tag: SdkUserAgentComponent
26+
private enum SdkUserAgentComponent: String, CaseIterable {
27+
case api = "api/s3"
28+
case lang = "lang/swift"
29+
case os = "os/"
30+
case sdk = "aws-sdk-swift/"
31+
}
32+
33+
/// Represents expected pieces of the User-Agent header of an URLRequest used for uploading or
34+
/// downloading.
35+
///
36+
/// Example SDK User-Agent:
37+
/// ```
38+
/// User-Agent: lib/amplify-swift
39+
/// ```
40+
/// - Tag: SdkUserAgentComponent
41+
private enum URLUserAgentComponent: String, CaseIterable {
42+
case lib = "lib/amplify-swift"
43+
case os = "os/"
44+
}
45+
46+
override func setUp() async throws {
47+
try await super.setUp()
48+
uploadedKeys = []
49+
}
50+
51+
override func tearDown() async throws {
52+
for key in uploadedKeys {
53+
_ = try await Amplify.Storage.remove(path: .fromString("public/\(key)"))
54+
}
55+
uploadedKeys = nil
56+
try await super.tearDown()
57+
}
58+
59+
/// Given: An data object
60+
/// When: Upload the data
61+
/// Then: The operation completes successfully
62+
func testUploadData() async throws {
63+
let key = UUID().uuidString
64+
let data = Data(key.utf8)
65+
66+
_ = try await Amplify.Storage.uploadData(path: .fromString("public/\(key)"), data: data, options: nil).value
67+
_ = try await Amplify.Storage.remove(path: .fromString("public/\(key)"))
68+
69+
// Only the remove operation results in an SDK request
70+
XCTAssertEqual(requestRecorder.sdkRequests.map { $0.method } , [.delete])
71+
try assertUserAgentComponents(sdkRequests: requestRecorder.sdkRequests)
72+
73+
XCTAssertEqual(requestRecorder.urlRequests.map { $0.httpMethod }, ["PUT"])
74+
try assertUserAgentComponents(urlRequests: requestRecorder.urlRequests)
75+
}
76+
77+
/// Given: A empty data object
78+
/// When: Upload the data
79+
/// Then: The operation completes successfully
80+
func testUploadEmptyData() async throws {
81+
let key = UUID().uuidString
82+
let data = Data("".utf8)
83+
_ = try await Amplify.Storage.uploadData(path: .fromString("public/\(key)"), data: data, options: nil).value
84+
_ = try await Amplify.Storage.remove(path: .fromString("public/\(key)"))
85+
86+
XCTAssertEqual(requestRecorder.urlRequests.map { $0.httpMethod }, ["PUT"])
87+
try assertUserAgentComponents(urlRequests: requestRecorder.urlRequests)
88+
}
89+
90+
/// Given: A file with contents
91+
/// When: Upload the file
92+
/// Then: The operation completes successfully and all URLSession and SDK requests include a user agent
93+
func testUploadFile() async throws {
94+
let key = UUID().uuidString
95+
let filePath = NSTemporaryDirectory() + key + ".tmp"
96+
97+
let fileURL = URL(fileURLWithPath: filePath)
98+
FileManager.default.createFile(atPath: filePath, contents: Data(key.utf8), attributes: nil)
99+
100+
_ = try await Amplify.Storage.uploadFile(path: .fromString("public/\(key)"), local: fileURL, options: nil).value
101+
_ = try await Amplify.Storage.remove(path: .fromString("public/\(key)"))
102+
103+
// Only the remove operation results in an SDK request
104+
XCTAssertEqual(requestRecorder.sdkRequests.map { $0.method} , [.delete])
105+
try assertUserAgentComponents(sdkRequests: requestRecorder.sdkRequests)
106+
107+
XCTAssertEqual(requestRecorder.urlRequests.map { $0.httpMethod }, ["PUT"])
108+
try assertUserAgentComponents(urlRequests: requestRecorder.urlRequests)
109+
}
110+
111+
/// Given: A file with empty contents
112+
/// When: Upload the file
113+
/// Then: The operation completes successfully
114+
func testUploadFileEmptyData() async throws {
115+
let key = UUID().uuidString
116+
let filePath = NSTemporaryDirectory() + key + ".tmp"
117+
let fileURL = URL(fileURLWithPath: filePath)
118+
FileManager.default.createFile(atPath: filePath, contents: Data("".utf8), attributes: nil)
119+
120+
_ = try await Amplify.Storage.uploadFile(path: .fromString("public/\(key)"), local: fileURL, options: nil).value
121+
_ = try await Amplify.Storage.remove(path: .fromString("public/\(key)"))
122+
123+
XCTAssertEqual(requestRecorder.urlRequests.map { $0.httpMethod }, ["PUT"])
124+
try assertUserAgentComponents(urlRequests: requestRecorder.urlRequests)
125+
}
126+
127+
/// Given: A large data object
128+
/// When: Upload the data
129+
/// Then: The operation completes successfully
130+
func testUploadLargeData() async throws {
131+
let key = "public/" + UUID().uuidString
132+
133+
let uploadKey = try await Amplify.Storage.uploadData(path: .fromString(key),
134+
data: AWSS3StoragePluginTestBase.largeDataObject,
135+
options: nil).value
136+
XCTAssertEqual(uploadKey, key)
137+
138+
try await Amplify.Storage.remove(path: .fromString(key))
139+
140+
let userAgents = requestRecorder.urlRequests.compactMap { $0.allHTTPHeaderFields?["User-Agent"] }
141+
XCTAssertGreaterThan(userAgents.count, 1)
142+
for userAgent in userAgents {
143+
let expectedComponent = "MultiPart/UploadPart"
144+
XCTAssertTrue(userAgent.contains(expectedComponent), "\(userAgent) does not contain \(expectedComponent)")
145+
}
146+
}
147+
148+
/// Given: A large file
149+
/// When: Upload the file
150+
/// Then: The operation completes successfully
151+
func testUploadLargeFile() async throws {
152+
let key = UUID().uuidString
153+
let filePath = NSTemporaryDirectory() + key + ".tmp"
154+
let fileURL = URL(fileURLWithPath: filePath)
155+
156+
FileManager.default.createFile(atPath: filePath,
157+
contents: AWSS3StoragePluginTestBase.largeDataObject,
158+
attributes: nil)
159+
160+
_ = try await Amplify.Storage.uploadFile(path: .fromString("public/\(key)"), local: fileURL, options: nil).value
161+
_ = try await Amplify.Storage.remove(path: .fromString("public/\(key)"))
162+
163+
let userAgents = requestRecorder.urlRequests.compactMap { $0.allHTTPHeaderFields?["User-Agent"] }
164+
XCTAssertGreaterThan(userAgents.count, 1)
165+
for userAgent in userAgents {
166+
let expectedComponent = "MultiPart/UploadPart"
167+
XCTAssertTrue(userAgent.contains(expectedComponent), "\(userAgent) does not contain \(expectedComponent)")
168+
}
169+
}
170+
171+
func removeIfExists(_ fileURL: URL) {
172+
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
173+
if fileExists {
174+
do {
175+
try FileManager.default.removeItem(at: fileURL)
176+
} catch {
177+
XCTFail("Failed to delete file at \(fileURL)")
178+
}
179+
}
180+
}
181+
182+
private func assertUserAgentComponents(sdkRequests: [SdkHttpRequest], file: StaticString = #filePath, line: UInt = #line) throws {
183+
for request in sdkRequests {
184+
let headers = request.headers.dictionary
185+
let userAgent = try XCTUnwrap(headers["User-Agent"]?.joined(separator:","))
186+
for component in SdkUserAgentComponent.allCases {
187+
XCTAssertTrue(userAgent.contains(component.rawValue), "\(userAgent.description) does not contain \(component)", file: file, line: line)
188+
}
189+
}
190+
}
191+
192+
private func assertUserAgentComponents(urlRequests: [URLRequest], file: StaticString = #filePath, line: UInt = #line) throws {
193+
for request in urlRequests {
194+
let headers = try XCTUnwrap(request.allHTTPHeaderFields)
195+
let userAgent = try XCTUnwrap(headers["User-Agent"])
196+
for component in URLUserAgentComponent.allCases {
197+
XCTAssertTrue(userAgent.contains(component.rawValue), "\(userAgent.description) does not contain \(component)", file: file, line: line)
198+
}
199+
}
200+
}
201+
}

AmplifyPlugins/Storage/Tests/StorageHostApp/StorageHostApp.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
68828E4628C2736C006E7C0A /* AWSS3StoragePluginProgressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08C28BEAF8E00C8A6EB /* AWSS3StoragePluginProgressTests.swift */; };
6060
68828E4728C27745006E7C0A /* AWSS3StoragePluginPutDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08828BEAF8E00C8A6EB /* AWSS3StoragePluginPutDataResumabilityTests.swift */; };
6161
68828E4828C2AAA6006E7C0A /* AWSS3StoragePluginGetDataResumabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684FB08B28BEAF8E00C8A6EB /* AWSS3StoragePluginGetDataResumabilityTests.swift */; };
62+
734605222BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734605212BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift */; };
63+
734605242BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734605232BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift */; };
6264
901AB3E92AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */; };
6365
97914BA32955798D002000EA /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEAF28E748270000C36A /* AsyncTesting.swift */; };
6466
97914BA52955798D002000EA /* AsyncExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEB028E748270000C36A /* AsyncExpectation.swift */; };
@@ -128,6 +130,8 @@
128130
684FB0A928BEB07200C8A6EB /* AWSS3StoragePluginIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AWSS3StoragePluginIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
129131
684FB0C228BEB45600C8A6EB /* AuthSignInHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSignInHelper.swift; sourceTree = "<group>"; };
130132
684FB0C528BEB84800C8A6EB /* StorageHostApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StorageHostApp.entitlements; sourceTree = "<group>"; };
133+
734605212BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginUploadIntegrationTests.swift; sourceTree = "<group>"; };
134+
734605232BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginDownloadIntegrationTests.swift; sourceTree = "<group>"; };
131135
901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3StoragePluginUploadMetadataTestCase.swift; sourceTree = "<group>"; };
132136
97914B972955797E002000EA /* StorageStressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageStressTests.swift; sourceTree = "<group>"; };
133137
97914BB92955798D002000EA /* StorageStressTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StorageStressTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -269,6 +273,8 @@
269273
562B9AA32A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift */,
270274
901AB3E82AE2C2DC000F825B /* AWSS3StoragePluginUploadMetadataTestCase.swift */,
271275
684FB08728BEAF8E00C8A6EB /* ResumabilityTests */,
276+
734605212BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift */,
277+
734605232BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift */,
272278
);
273279
path = AWSS3StoragePluginIntegrationTests;
274280
sourceTree = "<group>";
@@ -616,13 +622,15 @@
616622
68828E4628C2736C006E7C0A /* AWSS3StoragePluginProgressTests.swift in Sources */,
617623
684FB0B528BEB08900C8A6EB /* AWSS3StoragePluginAccessLevelTests.swift in Sources */,
618624
68828E4028C1549E006E7C0A /* AWSS3StoragePluginDownloadFileResumabilityTests.swift in Sources */,
625+
734605242BACB60E0039F0EB /* AWSS3StoragePluginDownloadIntegrationTests.swift in Sources */,
619626
68828E4528C26D2D006E7C0A /* AWSS3StoragePluginPrefixKeyResolverTests.swift in Sources */,
620627
684FB0B328BEB08900C8A6EB /* AWSS3StoragePluginTestBase.swift in Sources */,
621628
68828E3F28C1549B006E7C0A /* AWSS3StoragePluginUploadFileResumabilityTests.swift in Sources */,
622629
562B9AA42A0D703700A96FC6 /* AWSS3StoragePluginRequestRecorder.swift in Sources */,
623630
68828E3E28C1546F006E7C0A /* AWSS3StoragePluginConfigurationTests.swift in Sources */,
624631
68828E4728C27745006E7C0A /* AWSS3StoragePluginPutDataResumabilityTests.swift in Sources */,
625632
68828E4128C154E5006E7C0A /* AWSS3StoragePluginNegativeTests.swift in Sources */,
633+
734605222BACB5CC0039F0EB /* AWSS3StoragePluginUploadIntegrationTests.swift in Sources */,
626634
68828E3D28C136EB006E7C0A /* AWSS3StoragePluginBasicIntegrationTests.swift in Sources */,
627635
681DFEB428E748270000C36A /* XCTestCase+AsyncTesting.swift in Sources */,
628636
68828E4228C15B8B006E7C0A /* AWSS3StoragePluginOptionsUsabilityTests.swift in Sources */,

0 commit comments

Comments
 (0)