Skip to content

Commit 65757ce

Browse files
authored
fix(storage): delete file if key not found on download (#652)
1 parent f4a369c commit 65757ce

File tree

3 files changed

+64
-14
lines changed

3 files changed

+64
-14
lines changed

AmplifyPlugins/Storage/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService+DownloadBehavior.swift

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ extension AWSS3StorageService {
1919
let downloadTaskCreatedHandler = AWSS3StorageService.makeDownloadTaskCreatedHandler(onEvent: onEvent)
2020
let expression = AWSS3TransferUtilityDownloadExpression()
2121
expression.progressBlock = AWSS3StorageService.makeOnDownloadProgressHandler(onEvent: onEvent)
22-
let onDownloadCompletedHandler = AWSS3StorageService.makeDownloadCompletedHandler(onEvent: onEvent,
23-
serviceKey: serviceKey)
22+
let onDownloadCompletedHandler = AWSS3StorageService.makeDownloadCompletedHandler(fileURL: fileURL,
23+
serviceKey: serviceKey,
24+
onEvent: onEvent)
2425

2526
if let fileURL = fileURL {
2627
transferUtility.download(to: fileURL,
@@ -74,25 +75,27 @@ extension AWSS3StorageService {
7475
}
7576

7677
private static func makeDownloadCompletedHandler(
77-
onEvent: @escaping StorageServiceDownloadEventHandler,
78-
serviceKey: String) -> AWSS3TransferUtilityDownloadCompletionHandlerBlock {
78+
fileURL: URL? = nil,
79+
serviceKey: String,
80+
onEvent: @escaping StorageServiceDownloadEventHandler) -> AWSS3TransferUtilityDownloadCompletionHandlerBlock {
7981

8082
let block: AWSS3TransferUtilityDownloadCompletionHandlerBlock = { task, location, data, error in
8183
guard let response = task.response else {
8284
onEvent(StorageEvent.failed(StorageError.unknown("Missing HTTP Response")))
8385
return
8486
}
8587

86-
let storageError = StorageErrorHelper.mapHttpResponseCode(statusCode: response.statusCode,
87-
serviceKey: serviceKey)
88-
guard storageError == nil else {
89-
onEvent(StorageEvent.failed(storageError!))
88+
if let storageError = StorageErrorHelper.mapHttpResponseCode(statusCode: response.statusCode,
89+
serviceKey: serviceKey) {
90+
deleteFileIfKeyNotFound(storageError: storageError, fileURL: fileURL)
91+
onEvent(StorageEvent.failed(storageError))
9092
return
9193
}
9294

9395
guard error == nil else {
9496
let error = error! as NSError
9597
let storageError = StorageErrorHelper.mapTransferUtilityError(error)
98+
deleteFileIfKeyNotFound(storageError: storageError, fileURL: fileURL)
9699
onEvent(StorageEvent.failed(storageError))
97100
return
98101
}
@@ -102,4 +105,16 @@ extension AWSS3StorageService {
102105

103106
return block
104107
}
108+
109+
// TransferUtility saves the error response at the file location when the key cannot be found in S3
110+
// This is an best-effort attempt to ensure that the file is removed
111+
private static func deleteFileIfKeyNotFound(storageError: StorageError, fileURL: URL?) {
112+
if case .keyNotFound = storageError, let fileURL = fileURL {
113+
do {
114+
try FileManager.default.removeItem(at: fileURL)
115+
} catch {
116+
Amplify.Logging.error(error: error)
117+
}
118+
}
119+
}
105120
}

AmplifyPlugins/Storage/AWSS3StoragePluginFunctionalTests/AWSS3StoragePluginAccessLevelTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,9 @@ class AWSS3StoragePluginAccessLevelTests: AWSS3StoragePluginTestBase {
341341
let signInInvoked = expectation(description: "sign in completed")
342342
_ = Amplify.Auth.signIn(username: username, password: password) { event in
343343
switch event {
344-
case .completed:
344+
case .success:
345345
signInInvoked.fulfill()
346-
case .failed(let error):
346+
case .failure(let error):
347347
XCTFail("Failed to Sign in user \(error)")
348348
default:
349349
XCTFail("Unexpected event")
@@ -357,7 +357,7 @@ class AWSS3StoragePluginAccessLevelTests: AWSS3StoragePluginTestBase {
357357
var resultOptional: String?
358358
_ = Amplify.Auth.fetchAuthSession(listener: { event in
359359
switch event {
360-
case .completed(let authSession):
360+
case .success(let authSession):
361361
guard let cognitoAuthSession = authSession as? AuthCognitoIdentityProvider else {
362362
XCTFail("Could not get auth session as AuthCognitoIdentityProvider")
363363
return
@@ -369,7 +369,7 @@ class AWSS3StoragePluginAccessLevelTests: AWSS3StoragePluginTestBase {
369369
case .failure(let error):
370370
XCTFail("Failed to get auth session \(error)")
371371
}
372-
case .failed(let error):
372+
case .failure(let error):
373373
XCTFail("Failed to get auth session \(error)")
374374
default:
375375
XCTFail("Unexpected event")
@@ -387,9 +387,9 @@ class AWSS3StoragePluginAccessLevelTests: AWSS3StoragePluginTestBase {
387387
let signOutCompleted = expectation(description: "sign out completed")
388388
Amplify.Auth.signOut { event in
389389
switch event {
390-
case .completed:
390+
case .success:
391391
signOutCompleted.fulfill()
392-
case .failed(let error):
392+
case .failure(let error):
393393
XCTFail("Could not sign out user \(error)")
394394
default:
395395
XCTFail("Unexpected event")

AmplifyPlugins/Storage/AWSS3StoragePluginFunctionalTests/AWSS3StoragePluginNegativeTests.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,41 @@ class AWSS3StoragePluginNegativeTests: AWSS3StoragePluginTestBase {
4343
waitForExpectations(timeout: TestCommonConstants.networkTimeout)
4444
}
4545

46+
/// Given: Object does not exist in storage
47+
/// When: Call the downloadFile API with path to local file
48+
/// Then: Download fails and local file should not exist
49+
func testDownloadToFileNonexistentKey() {
50+
let key = UUID().uuidString
51+
let expectedKey = "public/" + key
52+
let filePath = NSTemporaryDirectory() + key + ".tmp"
53+
let fileURL = URL(fileURLWithPath: filePath)
54+
let failInvoked = expectation(description: "Failed is invoked")
55+
let operation = Amplify.Storage.downloadFile(
56+
key: key,
57+
local: fileURL,
58+
progressListener: nil) { event in
59+
switch event {
60+
case .success:
61+
XCTFail("Should not have completed successfully")
62+
case .failure(let error):
63+
guard case let .keyNotFound(key, _, _, _) = error else {
64+
XCTFail("Should have been validation error")
65+
return
66+
}
67+
68+
if FileManager.default.fileExists(atPath: fileURL.path) {
69+
XCTFail("local file should not exist")
70+
}
71+
72+
XCTAssertEqual(key, expectedKey)
73+
failInvoked.fulfill()
74+
}
75+
}
76+
77+
XCTAssertNotNil(operation)
78+
wait(for: [failInvoked], timeout: TestCommonConstants.networkTimeout)
79+
}
80+
4681
/// Given: A path to file that does not exist
4782
/// When: Upload the file
4883
/// Then: The operation fails with StorageError.missingLocalFile

0 commit comments

Comments
 (0)