Skip to content

Commit 8b91d06

Browse files
committed
Merge branch 'release/3.0.1'
2 parents 25504d1 + 4921ba1 commit 8b91d06

25 files changed

Lines changed: 511 additions & 73 deletions

Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,14 @@ class WebDAVSession {
221221
HTTPDebugLogger.logRequest(request)
222222
let progress = Progress(totalUnitCount: 1)
223223
let task = urlSession.downloadTask(with: request)
224-
onTaskCreation?(task)
225224
progress.addChild(task.progress, withPendingUnitCount: 1)
226225
let pendingPromise = Promise<HTTPURLResponse>.pending()
227226
let webDAVDownloadTask = WebDAVDownloadTask(promise: pendingPromise, localURL: localURL)
227+
// Register before invoking `onTaskCreation` so a synchronous resume cannot race the delegate's lookup.
228228
delegate?.addRunningDownloadTask(key: task, value: webDAVDownloadTask)
229-
if onTaskCreation == nil {
229+
if let onTaskCreation {
230+
onTaskCreation(task)
231+
} else {
230232
task.resume()
231233
}
232234
return pendingPromise
@@ -236,12 +238,14 @@ class WebDAVSession {
236238
HTTPDebugLogger.logRequest(request)
237239
let progress = Progress(totalUnitCount: 1)
238240
let task = urlSession.uploadTask(with: request, fromFile: fileURL)
239-
onTaskCreation?(task)
240241
progress.addChild(task.progress, withPendingUnitCount: 1)
241242
let pendingPromise = Promise<(HTTPURLResponse, Data?)>.pending()
242243
let webDAVDataTask = WebDAVDataTask(promise: pendingPromise)
244+
// Register before invoking `onTaskCreation` so a synchronous resume cannot race the delegate's lookup.
243245
delegate?.addRunningDataTask(key: task, value: webDAVDataTask)
244-
if onTaskCreation == nil {
246+
if let onTaskCreation {
247+
onTaskCreation(task)
248+
} else {
245249
task.resume()
246250
}
247251
return pendingPromise

Tests/CryptomatorCloudAccessIntegrationTests/Box/BoxCloudProviderIntegrationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class BoxCloudProviderIntegrationTests: CloudAccessIntegrationTestWithAuthentica
2222
private let credential = BoxCredentialMock()
2323

2424
override class func setUp() {
25-
integrationTestParentCloudPath = CloudPath("/iOS-IntegrationTests-Plain")
25+
integrationTestParentCloudPath = CloudPath("/iOS-IntegrationTests-Plain-\(runID)")
2626
let credential = BoxCredentialMock()
2727
// swiftlint:disable:next force_try
2828
setUpProvider = try! BoxCloudProvider(credential: credential)

Tests/CryptomatorCloudAccessIntegrationTests/CloudAccessIntegrationTest.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class CloudAccessIntegrationTest: XCTestCase {
1818
static let testContentForFilesInRoot = "testContent"
1919
static let testContentForFilesInTestFolder = "File inside Folder Content"
2020

21+
static let runID = String(UUID().uuidString.prefix(8))
2122
static var classSetUpError: Error?
2223
static var setUpProvider: CloudProvider!
2324
static var integrationTestParentCloudPath: CloudPath!
@@ -238,24 +239,34 @@ class CloudAccessIntegrationTest: XCTestCase {
238239
}
239240

240241
/// Retries an operation that fails due to eventual consistency (e.g. S3).
241-
/// During setUp, `parentFolderDoesNotExist` and `itemNotFound` indicate that a just-created parent
242-
/// folder or its directory metadata hasn't propagated yet.
242+
/// During setUp, `parentFolderDoesNotExist`, `itemNotFound`, and `itemAlreadyExists` indicate that
243+
/// a just-created or just-deleted item's state hasn't propagated yet.
243244
private static func retryOnEventualConsistencyError(maxAttempts: Int = 10, operation: () throws -> Void) throws {
244-
var lastError: Error = CloudProviderError.parentFolderDoesNotExist
245245
for attempt in 0 ..< maxAttempts {
246246
do {
247247
try operation()
248248
return
249-
} catch CloudProviderError.parentFolderDoesNotExist, CloudProviderError.itemNotFound {
250-
lastError = CloudProviderError.parentFolderDoesNotExist
249+
} catch {
250+
guard isEventualConsistencyError(error) else {
251+
throw error
252+
}
251253
if attempt == maxAttempts - 1 {
252-
throw lastError
254+
throw error
253255
}
254256
Thread.sleep(forTimeInterval: 2.0)
255257
}
256258
}
257259
}
258260

261+
private static func isEventualConsistencyError(_ error: Error) -> Bool {
262+
switch error {
263+
case CloudProviderError.parentFolderDoesNotExist, CloudProviderError.itemNotFound, CloudProviderError.itemAlreadyExists:
264+
return true
265+
default:
266+
return false
267+
}
268+
}
269+
259270
/// Polls `fetchItemList` until the expected number of items is visible, retrying up to 10 times with a 1-second delay.
260271
/// Used after setUp uploads test fixtures to wait for eventual consistency (e.g. S3, pCloud).
261272
static func waitForConsistency(provider: CloudProvider, folderPath: CloudPath, expectedItemCount: Int, attempt: Int = 0) -> Promise<Void> {

Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6BoxIntegrationTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ class VaultFormat6BoxIntegrationTests: CloudAccessIntegrationTest {
2222
private static let credential = BoxCredentialMock()
2323
// swiftlint:disable:next force_try
2424
private static let cloudProvider = try! BoxCloudProvider(credential: credential)
25-
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")
25+
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6-\(runID)")
2626

2727
override class func setUp() {
2828
integrationTestParentCloudPath = CloudPath("/")
29-
let setUpPromise = cloudProvider.deleteFolderIfExisting(at: vaultPath).then {
30-
DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest")
31-
}.then { decorator in
29+
let setUpPromise = DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest").then { decorator in
3230
setUpProvider = decorator
3331
}
3432
guard waitForPromises(timeout: 60.0) else {
@@ -42,6 +40,12 @@ class VaultFormat6BoxIntegrationTests: CloudAccessIntegrationTest {
4240
super.setUp()
4341
}
4442

43+
override class func tearDown() {
44+
super.tearDown()
45+
_ = cloudProvider.deleteFolderIfExisting(at: vaultPath)
46+
_ = waitForPromises(timeout: 60.0)
47+
}
48+
4549
override func setUpWithError() throws {
4650
try super.setUpWithError()
4751
let credential = BoxCredentialMock()

Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6DropboxIntegrationTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,11 @@ class VaultFormat6DropboxIntegrationTests: CloudAccessIntegrationTest {
2121

2222
private static let credential = DropboxCredentialMock()
2323
private static let cloudProvider = DropboxCloudProvider(credential: credential)
24-
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")
24+
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6-\(runID)")
2525

2626
override class func setUp() {
2727
integrationTestParentCloudPath = CloudPath("/")
28-
let setUpPromise = cloudProvider.deleteFolderIfExisting(at: vaultPath).then {
29-
DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest")
30-
}.then { decorator in
28+
let setUpPromise = DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest").then { decorator in
3129
setUpProvider = decorator
3230
}
3331
guard waitForPromises(timeout: 60.0) else {
@@ -41,6 +39,12 @@ class VaultFormat6DropboxIntegrationTests: CloudAccessIntegrationTest {
4139
super.setUp()
4240
}
4341

42+
override class func tearDown() {
43+
super.tearDown()
44+
_ = cloudProvider.deleteFolderIfExisting(at: vaultPath)
45+
_ = waitForPromises(timeout: 60.0)
46+
}
47+
4448
override func setUpWithError() throws {
4549
try super.setUpWithError()
4650
let credential = DropboxCredentialMock()

Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6GoogleDriveIntegrationTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ class VaultFormat6GoogleDriveIntegrationTests: CloudAccessIntegrationTest {
2222
private static let credential = GoogleDriveAuthenticatorMock.generateAuthorizedCredential(withRefreshToken: IntegrationTestSecrets.googleDriveRefreshToken, tokenUID: "IntegrationTest")
2323
// swiftlint:disable:next force_try
2424
private static let cloudProvider = try! GoogleDriveCloudProvider(credential: credential)
25-
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")
25+
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6-\(runID)")
2626

2727
override class func setUp() {
2828
integrationTestParentCloudPath = CloudPath("/")
29-
let setUpPromise = cloudProvider.deleteFolderIfExisting(at: vaultPath).then {
30-
DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest")
31-
}.then { decorator in
29+
let setUpPromise = DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest").then { decorator in
3230
setUpProvider = decorator
3331
}
3432
guard waitForPromises(timeout: 60.0) else {
@@ -42,6 +40,12 @@ class VaultFormat6GoogleDriveIntegrationTests: CloudAccessIntegrationTest {
4240
super.setUp()
4341
}
4442

43+
override class func tearDown() {
44+
super.tearDown()
45+
_ = cloudProvider.deleteFolderIfExisting(at: vaultPath)
46+
_ = waitForPromises(timeout: 60.0)
47+
}
48+
4549
override func setUpWithError() throws {
4650
try super.setUpWithError()
4751
let credential = GoogleDriveAuthenticatorMock.generateAuthorizedCredential(withRefreshToken: IntegrationTestSecrets.googleDriveRefreshToken, tokenUID: UUID().uuidString)

Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6MicrosoftGraphIntegrationTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ class VaultFormat6MicrosoftGraphIntegrationTests: CloudAccessIntegrationTest {
2222
private static let credential = MicrosoftGraphCredentialMock()
2323
// swiftlint:disable:next force_try
2424
private static let cloudProvider = try! MicrosoftGraphCloudProvider(credential: credential)
25-
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")
25+
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6-\(runID)")
2626

2727
override class func setUp() {
2828
integrationTestParentCloudPath = CloudPath("/")
29-
let setUpPromise = cloudProvider.deleteFolderIfExisting(at: vaultPath).then {
30-
DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest")
31-
}.then { decorator in
29+
let setUpPromise = DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest").then { decorator in
3230
setUpProvider = decorator
3331
}
3432
guard waitForPromises(timeout: 60.0) else {
@@ -42,6 +40,12 @@ class VaultFormat6MicrosoftGraphIntegrationTests: CloudAccessIntegrationTest {
4240
super.setUp()
4341
}
4442

43+
override class func tearDown() {
44+
super.tearDown()
45+
_ = cloudProvider.deleteFolderIfExisting(at: vaultPath)
46+
_ = waitForPromises(timeout: 60.0)
47+
}
48+
4549
override func setUpWithError() throws {
4650
try super.setUpWithError()
4751
let setUpPromise = DecoratorFactory.createFromExistingVaultFormat6(delegate: VaultFormat6MicrosoftGraphIntegrationTests.cloudProvider, vaultPath: VaultFormat6MicrosoftGraphIntegrationTests.vaultPath, password: "IntegrationTest").then { decorator in

Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6PCloudIntegrationTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@ class VaultFormat6PCloudIntegrationTests: CloudAccessIntegrationTest {
2424
private static let client = PCloud.createClient(with: credential.user)
2525
// swiftlint:disable:next force_try
2626
private static let cloudProvider = try! PCloudCloudProvider(client: client)
27-
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")
27+
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6-\(runID)")
2828

2929
override class func setUp() {
3030
integrationTestParentCloudPath = CloudPath("/")
31-
let setUpPromise = cloudProvider.deleteFolderIfExisting(at: vaultPath).then {
32-
DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest")
33-
}.then { decorator in
31+
let setUpPromise = DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest").then { decorator in
3432
setUpProvider = decorator
3533
}
3634
guard waitForPromises(timeout: 60.0) else {
@@ -52,6 +50,12 @@ class VaultFormat6PCloudIntegrationTests: CloudAccessIntegrationTest {
5250
}
5351
}
5452

53+
override class func tearDown() {
54+
super.tearDown()
55+
_ = cloudProvider.deleteFolderIfExisting(at: vaultPath)
56+
_ = waitForPromises(timeout: 60.0)
57+
}
58+
5559
override func setUpWithError() throws {
5660
try super.setUpWithError()
5761
let setUpPromise = DecoratorFactory.createFromExistingVaultFormat6(delegate: VaultFormat6PCloudIntegrationTests.cloudProvider, vaultPath: VaultFormat6PCloudIntegrationTests.vaultPath, password: "IntegrationTest").then { decorator in

Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6S3IntegrationTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@ class VaultFormat6S3IntegrationTests: CloudAccessIntegrationTest {
2222

2323
// swiftlint:disable:next force_try
2424
private static let cloudProvider = try! S3CloudProvider(credential: IntegrationTestSecrets.s3Credential)
25-
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")
25+
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6-\(runID)")
2626

2727
override class func setUp() {
2828
S3CloudProviderIntegrationTests.onetimeAWSIntegrationTestsSetup
2929

3030
integrationTestParentCloudPath = CloudPath("/")
31-
let setUpPromise = cloudProvider.deleteFolderIfExisting(at: vaultPath).then {
32-
DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest")
33-
}.then { decorator in
31+
let setUpPromise = DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest").then { decorator in
3432
setUpProvider = decorator
3533
}
3634
guard waitForPromises(timeout: 60.0) else {
@@ -58,6 +56,12 @@ class VaultFormat6S3IntegrationTests: CloudAccessIntegrationTest {
5856
}
5957
}
6058

59+
override class func tearDown() {
60+
super.tearDown()
61+
_ = cloudProvider.deleteFolderIfExisting(at: vaultPath)
62+
_ = waitForPromises(timeout: 60.0)
63+
}
64+
6165
override func setUpWithError() throws {
6266
try super.setUpWithError()
6367
let setUpPromise = DecoratorFactory.createFromExistingVaultFormat6(delegate: VaultFormat6S3IntegrationTests.cloudProvider, vaultPath: VaultFormat6S3IntegrationTests.vaultPath, password: "IntegrationTest").then { decorator in

Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6WebDAVIntegrationTests.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ class VaultFormat6WebDAVIntegrationTests: CloudAccessIntegrationTest {
2222
private static let client = WebDAVClient(credential: IntegrationTestSecrets.webDAVCredential)
2323
// swiftlint:disable:next force_try
2424
private static let cloudProvider = try! WebDAVProvider(with: client)
25-
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")
25+
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6-\(runID)")
2626

2727
override class func setUp() {
2828
integrationTestParentCloudPath = CloudPath("/")
29-
let setUpPromise = cloudProvider.deleteFolderIfExisting(at: vaultPath).then {
30-
DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest")
31-
}.then { decorator in
29+
let setUpPromise = DecoratorFactory.createNewVaultFormat6(delegate: cloudProvider, vaultPath: vaultPath, password: "IntegrationTest").then { decorator in
3230
setUpProvider = decorator
3331
}
3432
guard waitForPromises(timeout: 60.0) else {
@@ -42,6 +40,12 @@ class VaultFormat6WebDAVIntegrationTests: CloudAccessIntegrationTest {
4240
super.setUp()
4341
}
4442

43+
override class func tearDown() {
44+
super.tearDown()
45+
_ = cloudProvider.deleteFolderIfExisting(at: vaultPath)
46+
_ = waitForPromises(timeout: 60.0)
47+
}
48+
4549
override func setUpWithError() throws {
4650
try super.setUpWithError()
4751
let setUpPromise = DecoratorFactory.createFromExistingVaultFormat6(delegate: VaultFormat6WebDAVIntegrationTests.cloudProvider, vaultPath: VaultFormat6WebDAVIntegrationTests.vaultPath, password: "IntegrationTest").then { decorator in

0 commit comments

Comments
 (0)