From 2447291c54763c062cde45c4255e7fa2a912f423 Mon Sep 17 00:00:00 2001 From: Donny Wals Date: Wed, 21 Jan 2026 10:40:26 +0100 Subject: [PATCH 1/2] Implements #222 --- AGENTS.md | 2 +- CHANGELOG.md | 5 +++++ Sources/TUSKit/TUSClientError.swift | 6 +++--- Sources/TUSKit/Tasks/StatusTask.swift | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 203e81bd..bb7fe9a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ clarify uncertainty before coding, and align suggestions with the rules linked b - Use commits in `(): summary` format; squash fixups locally before sharing ## Testing -[Fill in by LLM assistant] +- `swift test`: Run the SwiftPM test suite ## Environment [Fill in by LLM assistant] diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8ef6d1..2fe2e60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 3.8.0 + +## Breaking changes +- `TUSClientError.couldNotGetFileStatus` now includes an `underlyingError` for better diagnostics. + # 3.7.0 - Removed cocoapods support diff --git a/Sources/TUSKit/TUSClientError.swift b/Sources/TUSKit/TUSClientError.swift index 61a23684..9615fa72 100644 --- a/Sources/TUSKit/TUSClientError.swift +++ b/Sources/TUSKit/TUSClientError.swift @@ -10,7 +10,7 @@ public enum TUSClientError: Error, LocalizedError { case couldNotStoreFileMetadata(underlyingError: Error) case couldNotCreateFileOnServer(underlyingError: Error) case couldNotUploadFile(underlyingError: Error) - case couldNotGetFileStatus + case couldNotGetFileStatus(underlyingError: Error) case fileSizeMismatchWithServer case couldNotDeleteFile(underlyingError: Error) case uploadIsAlreadyFinished @@ -40,8 +40,8 @@ public enum TUSClientError: Error, LocalizedError { return "Could not create file on server: (\(underlyingError.localizedDescription))" case .couldNotUploadFile(let underlyingError): return "Could not upload file: \(underlyingError.localizedDescription)" - case .couldNotGetFileStatus: - return "Could not get file status." + case .couldNotGetFileStatus(let underlyingError): + return "Could not get file status: \(underlyingError.localizedDescription)" case .fileSizeMismatchWithServer: return "File size mismatch with server." case .couldNotDeleteFile(let underlyingError): diff --git a/Sources/TUSKit/Tasks/StatusTask.swift b/Sources/TUSKit/Tasks/StatusTask.swift index 9c0401e9..ce22fb05 100644 --- a/Sources/TUSKit/Tasks/StatusTask.swift +++ b/Sources/TUSKit/Tasks/StatusTask.swift @@ -98,7 +98,7 @@ final class StatusTask: IdentifiableTask { } catch let error as TUSClientError { completed(.failure(error)) } catch { - completed(.failure(TUSClientError.couldNotGetFileStatus)) + completed(.failure(TUSClientError.couldNotGetFileStatus(underlyingError: error))) } } } From f58f9263e7db176f53cdc97739db815b9e6ccdc5 Mon Sep 17 00:00:00 2001 From: Donny Wals Date: Wed, 11 Feb 2026 10:18:53 +0100 Subject: [PATCH 2/2] test(tests): isolate mock/network state for parallel CI --- Tests/TUSKitTests/FilesTests.swift | 25 ++---- Tests/TUSKitTests/Mocks.swift | 90 ++++++++++++++++--- .../TUSKitTests/Support/NetworkSupport.swift | 32 +++---- Tests/TUSKitTests/Support/Support.swift | 12 ++- Tests/TUSKitTests/TUSAPITests.swift | 75 +++++++--------- .../TUSClient/TUSClientInternalTests.swift | 20 +++-- .../TUSClient/TUSClientTests.swift | 35 +++++--- .../TUSClient/TUSClient_CacheTests.swift | 19 ++-- .../TUSClient/TUSClient_ContextTests.swift | 21 +++-- .../TUSClient_CustomHeadersTests.swift | 19 ++-- .../TUSClient/TUSClient_DelegateTests.swift | 15 ++-- .../TUSClient_HeaderGenerationTests.swift | 52 ++++++----- .../TUSClient/TUSClient_IdsTests.swift | 23 +++-- .../TUSClient/TUSClient_RetryTests.swift | 27 ++++-- .../TUSClient/TUSClient_UploadingTests.swift | 66 ++++++++------ 15 files changed, 334 insertions(+), 197 deletions(-) diff --git a/Tests/TUSKitTests/FilesTests.swift b/Tests/TUSKitTests/FilesTests.swift index 63dadcee..7ffde57a 100644 --- a/Tests/TUSKitTests/FilesTests.swift +++ b/Tests/TUSKitTests/FilesTests.swift @@ -4,11 +4,14 @@ import XCTest final class FilesTests: XCTestCase { var files: Files! + var storagePath: URL! + override func setUp() { super.setUp() do { - files = try Files(storageDirectory: URL(string: "TUS")!) + storagePath = URL(string: "TUS-\(UUID().uuidString)")! + files = try Files(storageDirectory: storagePath) } catch { XCTFail("Could not instantiate Files") } @@ -17,25 +20,11 @@ final class FilesTests: XCTestCase { override func tearDown() { do { try files.clearCacheInStorageDirectory() - try emptyCacheDir() } catch { // Okay if dir doesn't exist } } - private func emptyCacheDir() throws { - - let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] - guard FileManager.default.fileExists(atPath: cacheDirectory.path, isDirectory: nil) else { - return - } - - for file in try FileManager.default.contentsOfDirectory(atPath: cacheDirectory.path) { - try FileManager.default.removeItem(atPath: cacheDirectory.appendingPathComponent(file).path) - } - - } - func testInitializers() { func removeTrailingSlash(url: URL) -> String { if url.absoluteString.last == "/" { @@ -127,11 +116,7 @@ final class FilesTests: XCTestCase { // Normally we write to the documents dir. But we explicitly are storing a file in a "wrong dir" // To see if retrieving metadata updates its directory. func writeDummyFileToCacheDir() throws -> URL { - let cacheURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] - .appendingPathComponent(Bundle.main.bundleIdentifier ?? "") - .appendingPathComponent("TUS") - let fileURL = cacheURL.appendingPathComponent("abcdefgh.txt") - return fileURL + files.storageDirectory.appendingPathComponent("abcdefgh.txt") } func storeMetaData(filePath: URL) throws -> URL { diff --git a/Tests/TUSKitTests/Mocks.swift b/Tests/TUSKitTests/Mocks.swift index 40638076..3583949c 100644 --- a/Tests/TUSKitTests/Mocks.swift +++ b/Tests/TUSKitTests/Mocks.swift @@ -72,12 +72,23 @@ final class TUSMockDelegate: TUSClientDelegate { } } - typealias Headers = [String: String]? +typealias Headers = [String: String]? /// MockURLProtocol to support mocking the network final class MockURLProtocol: URLProtocol { private static let queue = DispatchQueue(label: "com.tuskit.mockurlprotocol") + static let testIDHeader = "X-Mock-Test-ID" + + private struct ResponseKey: Hashable { + let method: String + let testID: String? + } + + private struct RequestRecord { + let testID: String? + let request: URLRequest + } struct Response { let status: Int @@ -85,13 +96,53 @@ final class MockURLProtocol: URLProtocol { let data: Data? } - static var responses = [String: (Headers) -> Response]() - static var receivedRequests = [URLRequest]() + private static var responses = [ResponseKey: (Headers) -> Response]() + private static var requestRecords = [RequestRecord]() + + static var receivedRequests: [URLRequest] { + get { + queue.sync { + requestRecords.map(\.request) + } + } + set { + queue.sync { + requestRecords = newValue.map { request in + RequestRecord(testID: request.value(forHTTPHeaderField: testIDHeader), request: request) + } + } + } + } + + static func receivedRequests(testID: String?) -> [URLRequest] { + queue.sync { + requestRecords + .filter { $0.testID == testID } + .map(\.request) + } + } - static func reset() { - queue.async { - responses = [:] - receivedRequests = [] + static func reset(testID: String? = nil) { + queue.sync { + guard let testID else { + responses = [:] + requestRecords = [] + return + } + + responses = responses.filter { $0.key.testID != testID } + requestRecords.removeAll { $0.testID == testID } + } + } + + static func clearReceivedRequests(testID: String? = nil) { + queue.sync { + guard let testID else { + requestRecords = [] + return + } + + requestRecords.removeAll { $0.testID == testID } } } @@ -99,9 +150,10 @@ final class MockURLProtocol: URLProtocol { /// - Parameters: /// - method: The http method (POST PATCH etc) /// - makeResponse: A closure that returns a Response - static func prepareResponse(for method: String, makeResponse: @escaping (Headers) -> Response) { - queue.async { - responses[method] = makeResponse + static func prepareResponse(for method: String, testID: String? = nil, makeResponse: @escaping (Headers) -> Response) { + let key = ResponseKey(method: method, testID: testID) + queue.sync { + responses[key] = makeResponse } } @@ -123,17 +175,27 @@ final class MockURLProtocol: URLProtocol { guard let client = self.client else { return } guard let method = self.request.httpMethod, - let preparedResponseClosure = type(of: self).responses[method] else { + let preparedResponseClosure = { + let testID = self.request.value(forHTTPHeaderField: type(of: self).testIDHeader) + let key = ResponseKey(method: method, testID: testID) + let fallbackKey = ResponseKey(method: method, testID: nil) + return type(of: self).responses[key] ?? type(of: self).responses[fallbackKey] + }() else { // assertionFailure("No response found for \(String(describing: request.httpMethod)) prepared \(type(of: self).responses)") return } let preparedResponse = preparedResponseClosure(self.request.allHTTPHeaderFields) - type(of: self).receivedRequests.append(self.request) + type(of: self).requestRecords.append( + RequestRecord( + testID: self.request.value(forHTTPHeaderField: type(of: self).testIDHeader), + request: self.request + ) + ) - let url = URL(string: "https://tusd.tusdemo.net/files")! - let response = HTTPURLResponse(url: url, statusCode: preparedResponse.status, httpVersion: nil, headerFields: preparedResponse.headers)! + let responseURL = self.request.url ?? URL(string: "https://tusd.tusdemo.net/files")! + let response = HTTPURLResponse(url: responseURL, statusCode: preparedResponse.status, httpVersion: nil, headerFields: preparedResponse.headers)! client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) diff --git a/Tests/TUSKitTests/Support/NetworkSupport.swift b/Tests/TUSKitTests/Support/NetworkSupport.swift index dc2489e9..88c32f26 100644 --- a/Tests/TUSKitTests/Support/NetworkSupport.swift +++ b/Tests/TUSKitTests/Support/NetworkSupport.swift @@ -4,13 +4,13 @@ import XCTest /// Server gives inappropriorate offsets /// - Parameter data: Data to upload -func prepareNetworkForWrongOffset(data: Data) { - MockURLProtocol.prepareResponse(for: "POST") { _ in +func prepareNetworkForWrongOffset(data: Data, testID: String? = nil) { + MockURLProtocol.prepareResponse(for: "POST", testID: testID) { _ in MockURLProtocol.Response(status: 200, headers: ["Location": "www.somefakelocation.com"], data: nil) } // Mimick chunk uploading with offsets - MockURLProtocol.prepareResponse(for: "PATCH") { headers in + MockURLProtocol.prepareResponse(for: "PATCH", testID: testID) { headers in guard let headers = headers, let strOffset = headers["Upload-Offset"], @@ -27,8 +27,8 @@ func prepareNetworkForWrongOffset(data: Data) { } } -func prepareNetworkForSuccesfulUploads(data: Data, lowerCasedKeysInResponses: Bool = false) { - MockURLProtocol.prepareResponse(for: "POST") { _ in +func prepareNetworkForSuccesfulUploads(data: Data, lowerCasedKeysInResponses: Bool = false, testID: String? = nil) { + MockURLProtocol.prepareResponse(for: "POST", testID: testID) { _ in let key: String if lowerCasedKeysInResponses { key = "location" @@ -39,7 +39,7 @@ func prepareNetworkForSuccesfulUploads(data: Data, lowerCasedKeysInResponses: Bo } // Mimick chunk uploading with offsets - MockURLProtocol.prepareResponse(for: "PATCH") { headers in + MockURLProtocol.prepareResponse(for: "PATCH", testID: testID) { headers in guard let headers = headers, let strOffset = headers["Upload-Offset"], @@ -64,33 +64,33 @@ func prepareNetworkForSuccesfulUploads(data: Data, lowerCasedKeysInResponses: Bo } -func prepareNetworkForErronousResponses() { - MockURLProtocol.prepareResponse(for: "POST") { _ in +func prepareNetworkForErronousResponses(testID: String? = nil) { + MockURLProtocol.prepareResponse(for: "POST", testID: testID) { _ in MockURLProtocol.Response(status: 401, headers: [:], data: nil) } - MockURLProtocol.prepareResponse(for: "PATCH") { _ in + MockURLProtocol.prepareResponse(for: "PATCH", testID: testID) { _ in MockURLProtocol.Response(status: 401, headers: [:], data: nil) } - MockURLProtocol.prepareResponse(for: "HEAD") { _ in + MockURLProtocol.prepareResponse(for: "HEAD", testID: testID) { _ in MockURLProtocol.Response(status: 401, headers: [:], data: nil) } } -func prepareNetworkForSuccesfulStatusCall(data: Data) { - MockURLProtocol.prepareResponse(for: "HEAD") { _ in +func prepareNetworkForSuccesfulStatusCall(data: Data, testID: String? = nil) { + MockURLProtocol.prepareResponse(for: "HEAD", testID: testID) { _ in MockURLProtocol.Response(status: 200, headers: ["Upload-Length": String(data.count), "Upload-Offset": "0"], data: nil) } } /// Create call can still succeed. This is useful for triggering a status call. -func prepareNetworkForFailingUploads() { +func prepareNetworkForFailingUploads(testID: String? = nil) { // Upload means patch. Letting that fail. - MockURLProtocol.prepareResponse(for: "PATCH") { _ in + MockURLProtocol.prepareResponse(for: "PATCH", testID: testID) { _ in MockURLProtocol.Response(status: 401, headers: [:], data: nil) } } -func resetReceivedRequests() { - MockURLProtocol.receivedRequests = [] +func resetReceivedRequests(testID: String? = nil) { + MockURLProtocol.clearReceivedRequests(testID: testID) } diff --git a/Tests/TUSKitTests/Support/Support.swift b/Tests/TUSKitTests/Support/Support.swift index 2344f90f..7066bf66 100644 --- a/Tests/TUSKitTests/Support/Support.swift +++ b/Tests/TUSKitTests/Support/Support.swift @@ -30,15 +30,23 @@ func clearDirectory(dir: URL) { } } -func makeClient(storagePath: URL?, supportedExtensions: [TUSProtocolExtension] = [.creation]) -> TUSClient { +func makeClient(storagePath: URL?, + supportedExtensions: [TUSProtocolExtension] = [.creation], + sessionIdentifier: String = "TEST", + mockTestID: String? = nil) -> TUSClient { let liveDemoPath = URL(string: "https://tusd.tusdemo.net/files")! // We don't use a live URLSession, we mock it out. let configuration = URLSessionConfiguration.default configuration.protocolClasses = [MockURLProtocol.self] + if let mockTestID { + var headers = configuration.httpAdditionalHeaders as? [String: String] ?? [:] + headers[MockURLProtocol.testIDHeader] = mockTestID + configuration.httpAdditionalHeaders = headers + } do { let client = try TUSClient(server: liveDemoPath, - sessionIdentifier: "TEST", + sessionIdentifier: sessionIdentifier, sessionConfiguration: configuration, storageDirectory: storagePath, supportedExtensions: supportedExtensions) diff --git a/Tests/TUSKitTests/TUSAPITests.swift b/Tests/TUSKitTests/TUSAPITests.swift index 281d5537..7b0ffce0 100644 --- a/Tests/TUSKitTests/TUSAPITests.swift +++ b/Tests/TUSKitTests/TUSAPITests.swift @@ -14,25 +14,32 @@ final class TUSAPITests: XCTestCase { var api: TUSAPI! var uploadURL: URL! + var mockTestID: String! override func setUp() { super.setUp() + mockTestID = UUID().uuidString + MockURLProtocol.reset(testID: mockTestID) + let configuration = URLSessionConfiguration.default configuration.protocolClasses = [MockURLProtocol.self] + if let mockTestID { + configuration.httpAdditionalHeaders = [MockURLProtocol.testIDHeader: mockTestID] + } uploadURL = URL(string: "www.tus.io")! api = TUSAPI(sessionConfiguration: configuration) } override func tearDown() { super.tearDown() - MockURLProtocol.receivedRequests = [] + MockURLProtocol.reset(testID: mockTestID) } func testStatus() throws { let length = 3000 let offset = 20 - MockURLProtocol.prepareResponse(for: "HEAD") { _ in + MockURLProtocol.prepareResponse(for: "HEAD", testID: mockTestID) { _ in MockURLProtocol.Response(status: 200, headers: ["Upload-Length": String(length), "Upload-Offset": String(offset)], data: nil) } @@ -60,7 +67,7 @@ final class TUSAPITests: XCTestCase { func testCreationWithAbsolutePath() throws { let remoteFileURL = URL(string: "https://tus.io/myfile")! - MockURLProtocol.prepareResponse(for: "POST") { _ in + MockURLProtocol.prepareResponse(for: "POST", testID: mockTestID) { _ in MockURLProtocol.Response(status: 200, headers: ["Location": remoteFileURL.absoluteString], data: nil) } @@ -82,23 +89,18 @@ final class TUSAPITests: XCTestCase { waitForExpectations(timeout: 3, handler: nil) - let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests.first?.allHTTPHeaderFields) + let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests(testID: mockTestID).first?.allHTTPHeaderFields) let expectedFileName = metaData.filePath.lastPathComponent.toBase64() - let expectedHeaders: [String: String] = - [ - "TUS-Resumable": "1.0.0", - "Upload-Length": String(size), - "Upload-Metadata": "filename \(expectedFileName)" - ] - - XCTAssertEqual(expectedHeaders, headerFields) + XCTAssertEqual("1.0.0", headerFields["TUS-Resumable"]) + XCTAssertEqual(String(size), headerFields["Upload-Length"]) + XCTAssertEqual("filename \(expectedFileName)", headerFields["Upload-Metadata"]) } func testCreationWithRelativePath() throws { let uploadURL = URL(string: "https://tus.example.org/files")! let relativePath = "files/24e533e02ec3bc40c387f1a0e460e216" let expectedURL = URL(string: "https://tus.example.org/files/24e533e02ec3bc40c387f1a0e460e216")! - MockURLProtocol.prepareResponse(for: "POST") { _ in + MockURLProtocol.prepareResponse(for: "POST", testID: mockTestID) { _ in MockURLProtocol.Response(status: 200, headers: ["Location": relativePath], data: nil) } @@ -120,21 +122,16 @@ final class TUSAPITests: XCTestCase { waitForExpectations(timeout: 3, handler: nil) - let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests.first?.allHTTPHeaderFields) + let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests(testID: mockTestID).first?.allHTTPHeaderFields) let expectedFileName = metaData.filePath.lastPathComponent.toBase64() - let expectedHeaders: [String: String] = - [ - "TUS-Resumable": "1.0.0", - "Upload-Length": String(size), - "Upload-Metadata": "filename \(expectedFileName)" - ] - - XCTAssertEqual(expectedHeaders, headerFields) + XCTAssertEqual("1.0.0", headerFields["TUS-Resumable"]) + XCTAssertEqual(String(size), headerFields["Upload-Length"]) + XCTAssertEqual("filename \(expectedFileName)", headerFields["Upload-Metadata"]) } func testUpload() throws { let data = Data("Hello how are you".utf8) - MockURLProtocol.prepareResponse(for: "PATCH") { _ in + MockURLProtocol.prepareResponse(for: "PATCH", testID: mockTestID) { _ in MockURLProtocol.Response(status: 200, headers: ["Upload-Offset": String(data.count)], data: nil) } @@ -154,16 +151,11 @@ final class TUSAPITests: XCTestCase { waitForExpectations(timeout: 3, handler: nil) - let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests.first?.allHTTPHeaderFields) - let expectedHeaders: [String: String] = - [ - "TUS-Resumable": "1.0.0", - "Content-Type": "application/offset+octet-stream", - "Upload-Offset": String(offset), - "Content-Length": String(length) - ] - - XCTAssertEqual(headerFields, expectedHeaders) + let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests(testID: mockTestID).first?.allHTTPHeaderFields) + XCTAssertEqual("1.0.0", headerFields["TUS-Resumable"]) + XCTAssertEqual("application/offset+octet-stream", headerFields["Content-Type"]) + XCTAssertEqual(String(offset), headerFields["Upload-Offset"]) + XCTAssertEqual(String(length), headerFields["Content-Length"]) } func testUploadWithRelativePath() throws { @@ -172,7 +164,7 @@ final class TUSAPITests: XCTestCase { let relativePath = "files/24e533e02ec3bc40c387f1a0e460e216" let uploadURL = URL(string: relativePath, relativeTo: baseURL)! let expectedURL = URL(string: "https://tus.example.org/files/24e533e02ec3bc40c387f1a0e460e216")! - MockURLProtocol.prepareResponse(for: "PATCH") { _ in + MockURLProtocol.prepareResponse(for: "PATCH", testID: mockTestID) { _ in MockURLProtocol.Response(status: 200, headers: ["Upload-Offset": String(data.count)], data: nil) } @@ -192,16 +184,11 @@ final class TUSAPITests: XCTestCase { waitForExpectations(timeout: 3, handler: nil) - let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests.first?.allHTTPHeaderFields) - let expectedHeaders: [String: String] = - [ - "TUS-Resumable": "1.0.0", - "Content-Type": "application/offset+octet-stream", - "Upload-Offset": String(offset), - "Content-Length": String(length) - ] - - XCTAssertEqual(headerFields, expectedHeaders) + let headerFields = try XCTUnwrap(MockURLProtocol.receivedRequests(testID: mockTestID).first?.allHTTPHeaderFields) + XCTAssertEqual("1.0.0", headerFields["TUS-Resumable"]) + XCTAssertEqual("application/offset+octet-stream", headerFields["Content-Type"]) + XCTAssertEqual(String(offset), headerFields["Upload-Offset"]) + XCTAssertEqual(String(length), headerFields["Content-Length"]) } } diff --git a/Tests/TUSKitTests/TUSClient/TUSClientInternalTests.swift b/Tests/TUSKitTests/TUSClient/TUSClientInternalTests.swift index 7cf0a0da..3b60de2d 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClientInternalTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClientInternalTests.swift @@ -16,30 +16,34 @@ final class TUSClientInternalTests: XCTestCase { var fullStoragePath: URL! var data: Data! var files: Files! + var mockTestID: String! override func setUp() { super.setUp() do { - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString files = try Files(storageDirectory: relativeStoragePath) fullStoragePath = files.storageDirectory clearDirectory(dir: files.storageDirectory) data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate } catch { XCTFail("Could not instantiate Files \(error)") } - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) } override func tearDown() { super.tearDown() - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] clearDirectory(dir: cacheDir) @@ -67,7 +71,9 @@ final class TUSClientInternalTests: XCTestCase { contents = try FileManager.default.contentsOfDirectory(at: fullStoragePath, includingPropertiesForKeys: nil) XCTAssertFalse(contents.isEmpty) - client = makeClient(storagePath: fullStoragePath) + client = makeClient(storagePath: fullStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) contents = try FileManager.default.contentsOfDirectory(at: fullStoragePath, includingPropertiesForKeys: nil) XCTAssertFalse(contents.isEmpty, "The client is expected to NOT remove unfinished uploads on startup") } @@ -83,7 +89,9 @@ final class TUSClientInternalTests: XCTestCase { contents = try FileManager.default.contentsOfDirectory(at: fullStoragePath, includingPropertiesForKeys: nil) XCTAssertFalse(contents.isEmpty) - client = makeClient(storagePath: fullStoragePath) + client = makeClient(storagePath: fullStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) contents = try FileManager.default.contentsOfDirectory(at: fullStoragePath, includingPropertiesForKeys: nil) XCTAssertFalse(contents.isEmpty, "The client is expected to NOT remove finished uploads on startup") } diff --git a/Tests/TUSKitTests/TUSClient/TUSClientTests.swift b/Tests/TUSKitTests/TUSClient/TUSClientTests.swift index b700c068..43331136 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClientTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClientTests.swift @@ -8,13 +8,19 @@ final class TUSClientTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! + + private var receivedRequests: [URLRequest] { + MockURLProtocol.receivedRequests(testID: mockTestID) + } override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -23,7 +29,9 @@ final class TUSClientTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -32,11 +40,12 @@ final class TUSClientTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } @@ -114,8 +123,11 @@ final class TUSClientTests: XCTestCase { // MARK: - Supported Extensions func testClientExcludesCreationStep() throws { - prepareNetworkForSuccesfulStatusCall(data: data) - client = makeClient(storagePath: fullStoragePath, supportedExtensions: []) + prepareNetworkForSuccesfulStatusCall(data: data, testID: mockTestID) + client = makeClient(storagePath: fullStoragePath, + supportedExtensions: [], + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) client.delegate = tusDelegate // Act @@ -123,15 +135,18 @@ final class TUSClientTests: XCTestCase { waitForUploadsToFinish() // Assert (ensure that the create HTTP request has not been called) - XCTAssertFalse(MockURLProtocol.receivedRequests.contains(where: { + XCTAssertFalse(receivedRequests.contains(where: { $0.httpMethod == "POST" && $0.url?.absoluteString == "https://tusd.tusdemo.net/files" })) } func testClientIncludesCreationStep() throws { - prepareNetworkForSuccesfulStatusCall(data: data) - client = makeClient(storagePath: fullStoragePath, supportedExtensions: [.creation]) + prepareNetworkForSuccesfulStatusCall(data: data, testID: mockTestID) + client = makeClient(storagePath: fullStoragePath, + supportedExtensions: [.creation], + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) client.delegate = tusDelegate // Act @@ -139,7 +154,7 @@ final class TUSClientTests: XCTestCase { waitForUploadsToFinish() // Assert (ensure that the create HTTP request has not been called) - XCTAssert(MockURLProtocol.receivedRequests.contains(where: { + XCTAssert(receivedRequests.contains(where: { $0.httpMethod == "POST" && $0.url?.absoluteString == "https://tusd.tusdemo.net/files" })) diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_CacheTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_CacheTests.swift index 9bd7859c..530c6e5a 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_CacheTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_CacheTests.swift @@ -8,13 +8,15 @@ final class TUSClient_CacheTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] .appendingPathComponent(Bundle.main.bundleIdentifier ?? "") @@ -24,7 +26,9 @@ final class TUSClient_CacheTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -33,11 +37,12 @@ final class TUSClient_CacheTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } @@ -91,13 +96,15 @@ final class TUSClient_CacheTests: XCTestCase { // Then the file can be deleted right after fetching the status. // Create isolated dir for this test, in case of parallelism issues. - let storagePath = URL(string: "DELETE_ME")! + let storagePath = URL(string: "DELETE_ME-\(UUID().uuidString)")! let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let fullStoragePath = docDir.appendingPathComponent(storagePath.absoluteString) clearDirectory(dir: fullStoragePath) - client = makeClient(storagePath: fullStoragePath) + client = makeClient(storagePath: fullStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_ContextTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_ContextTests.swift index a03b3940..322f661a 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_ContextTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_ContextTests.swift @@ -8,13 +8,19 @@ final class TUSClient_ContextTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! + + private var receivedRequests: [URLRequest] { + MockURLProtocol.receivedRequests(testID: mockTestID) + } override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -23,7 +29,9 @@ final class TUSClient_ContextTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -32,12 +40,13 @@ final class TUSClient_ContextTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() client.stopAndCancelAll() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } @@ -88,7 +97,7 @@ final class TUSClient_ContextTests: XCTestCase { } func testContextIsGivenOnFailure() throws { - prepareNetworkForFailingUploads() + prepareNetworkForFailingUploads(testID: mockTestID) let expectedContext = ["I am a key" : "I am a value"] @@ -125,7 +134,7 @@ final class TUSClient_ContextTests: XCTestCase { wait(for: [startedExpectation], timeout: 5) // Validate - let createRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "POST" } + let createRequests = receivedRequests.filter { $0.httpMethod == "POST" } for request in createRequests { let headers = try XCTUnwrap(request.allHTTPHeaderFields) diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_CustomHeadersTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_CustomHeadersTests.swift index 3de81c13..e659bc87 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_CustomHeadersTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_CustomHeadersTests.swift @@ -8,13 +8,19 @@ final class TUSClient_CustomHeadersTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! + + private var receivedRequests: [URLRequest] { + MockURLProtocol.receivedRequests(testID: mockTestID) + } override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -23,7 +29,9 @@ final class TUSClient_CustomHeadersTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -32,11 +40,12 @@ final class TUSClient_CustomHeadersTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } @@ -66,7 +75,7 @@ final class TUSClient_CustomHeadersTests: XCTestCase { wait(for: [startedExpectation], timeout: 5) // Validate - let createRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "POST" } + let createRequests = receivedRequests.filter { $0.httpMethod == "POST" } for request in createRequests { let headers = try XCTUnwrap(request.allHTTPHeaderFields) diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_DelegateTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_DelegateTests.swift index 604cea2e..21d43bf4 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_DelegateTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_DelegateTests.swift @@ -8,13 +8,15 @@ final class TUSClient_DelegateTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -23,7 +25,9 @@ final class TUSClient_DelegateTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -32,11 +36,12 @@ final class TUSClient_DelegateTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } @@ -54,7 +59,7 @@ final class TUSClient_DelegateTests: XCTestCase { func testStartedUploadIsCalledOnceForLargeFileWhenUploadFails() throws { - prepareNetworkForFailingUploads() + prepareNetworkForFailingUploads(testID: mockTestID) // Even when retrying, start should only be called once. let data = Fixtures.makeLargeData() diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_HeaderGenerationTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_HeaderGenerationTests.swift index 5e2bdbc7..8dfa96f1 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_HeaderGenerationTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_HeaderGenerationTests.swift @@ -9,13 +9,19 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! + + private var receivedRequests: [URLRequest] { + MockURLProtocol.receivedRequests(testID: mockTestID) + } override func setUp() { super.setUp() relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -25,11 +31,12 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { data = Data("abcdef".utf8) tusDelegate = TUSMockDelegate() - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) client = nil } @@ -64,7 +71,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { /// Verifies the generator is called even when no custom headers were supplied, enabling clients to inject headers. func testGenerateHeadersCalledWithoutCustomHeaders() throws { let configuration = makeConfiguration() - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) let generatorCalled = expectation(description: "Header generator should be invoked") tusDelegate.finishUploadExpectation = expectation(description: "Upload finished") @@ -85,7 +92,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { wait(for: [generatorCalled, tusDelegate.finishUploadExpectation!], timeout: 5) XCTAssertEqual(firstReceivedHeaders ?? [:], [:]) - let createRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "POST" } + let createRequests = receivedRequests.filter { $0.httpMethod == "POST" } XCTAssertFalse(createRequests.isEmpty) guard let postHeaders = createRequests.first?.allHTTPHeaderFields else { XCTFail("Expected POST request headers") @@ -98,8 +105,8 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { func testGenerateHeadersReceivesLastAppliedValuesOnAutomaticRetry() throws { let configuration = makeConfiguration() - prepareNetworkForSuccesfulUploads(data: data) - prepareNetworkForFailingUploads() + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) + prepareNetworkForFailingUploads(testID: mockTestID) var receivedAuthorizationHeaders: [String] = [] let generatorCalledTwice = expectation(description: "Header generator called twice") @@ -133,7 +140,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { func testGenerateHeadersCalledWhenResumingUpload() throws { let configuration = makeConfiguration() - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) let firstGeneratorCalled = expectation(description: "First header generator called") let uploadStarted = expectation(description: "Upload started before pausing") @@ -153,9 +160,9 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { wait(for: [firstGeneratorCalled, uploadStarted], timeout: 5) client.stopAndCancelAll() - MockURLProtocol.reset() - prepareNetworkForSuccesfulStatusCall(data: data) - prepareNetworkForSuccesfulUploads(data: data) + MockURLProtocol.reset(testID: mockTestID) + prepareNetworkForSuccesfulStatusCall(data: data, testID: mockTestID) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) tusDelegate = TUSMockDelegate() let finishExpectation = expectation(description: "Upload should finish after resume") @@ -178,7 +185,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { /// Ensures uploads wait for asynchronous header generation before proceeding. func testGenerateHeadersSupportsAsynchronousCompletion() throws { let configuration = makeConfiguration() - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) let asyncExpectation = expectation(description: "Async header generator invoked") asyncExpectation.expectedFulfillmentCount = 2 @@ -200,7 +207,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { /// Ensures the generator only receives caller-supplied headers during upload creation. func testGenerateHeadersReceivesOnlyCustomHeadersDuringCreate() throws { let configuration = makeConfiguration() - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) let customHeaders = ["Authorization": "Bearer foo", "X-Trace": "123"] var observedHeaders: [[String: String]] = [] @@ -227,7 +234,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { /// Ensures the generator only receives caller-supplied headers when a status check runs. func testGenerateHeadersReceivesOnlyCustomHeadersDuringStatus() throws { let configuration = makeConfiguration() - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) let customHeaders = ["Authorization": "Bearer bar", "X-Trace": "resume-123"] tusDelegate.startUploadExpectation = expectation(description: "Upload started") @@ -241,9 +248,9 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { wait(for: [tusDelegate.startUploadExpectation!], timeout: 5) client.stopAndCancelAll() - MockURLProtocol.reset() - prepareNetworkForSuccesfulStatusCall(data: data) - prepareNetworkForSuccesfulUploads(data: data) + MockURLProtocol.reset(testID: mockTestID) + prepareNetworkForSuccesfulStatusCall(data: data, testID: mockTestID) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) tusDelegate = TUSMockDelegate() let statusGeneratorCalled = expectation(description: "Header generator called during status") @@ -269,7 +276,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { /// Ensures the generator only receives caller-supplied headers during the upload (PATCH) step. func testGenerateHeadersReceivesOnlyCustomHeadersDuringUpload() throws { let configuration = makeConfiguration() - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) let customHeaders = ["Authorization": "Bearer data", "X-Trace": "upload-step"] tusDelegate.finishUploadExpectation = expectation(description: "Upload finished") @@ -290,8 +297,8 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { /// Ensures the generator cannot override headers that TUSClient manages itself. func testGenerateHeadersCannotOverrideReservedHeaders() throws { let configuration = makeConfiguration() - MockURLProtocol.reset() - prepareNetworkForSuccesfulUploads(data: data) + MockURLProtocol.reset(testID: mockTestID) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) let customHeaders = ["Authorization": "Bearer original"] tusDelegate.finishUploadExpectation = expectation(description: "Upload finished") @@ -307,7 +314,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { _ = try client.upload(data: data, customHeaders: customHeaders) wait(for: [tusDelegate.finishUploadExpectation!], timeout: 5) - let patchRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "PATCH" } + let patchRequests = receivedRequests.filter { $0.httpMethod == "PATCH" } XCTAssertFalse(patchRequests.isEmpty) guard let patchHeaders = patchRequests.first?.allHTTPHeaderFields else { XCTFail("Expected PATCH request headers") @@ -320,6 +327,9 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { private func makeConfiguration() -> URLSessionConfiguration { let configuration = URLSessionConfiguration.default configuration.protocolClasses = [MockURLProtocol.self] + if let mockTestID { + configuration.httpAdditionalHeaders = [MockURLProtocol.testIDHeader: mockTestID] + } return configuration } @@ -328,7 +338,7 @@ final class TUSClient_HeaderGenerationTests: XCTestCase { let configuration = configuration ?? makeConfiguration() return try TUSClient( server: URL(string: "https://tusd.tusdemo.net/files")!, - sessionIdentifier: "TEST", + sessionIdentifier: "TEST-\(mockTestID!)", sessionConfiguration: configuration, storageDirectory: relativeStoragePath, supportedExtensions: [.creation], diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_IdsTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_IdsTests.swift index b929671d..aaa9ee17 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_IdsTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_IdsTests.swift @@ -8,13 +8,15 @@ final class TUSClient_IdsTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -23,7 +25,9 @@ final class TUSClient_IdsTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -32,11 +36,12 @@ final class TUSClient_IdsTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } @@ -44,7 +49,7 @@ final class TUSClient_IdsTests: XCTestCase { func testUploadIdsArePreservedBetweenSessions() throws { // Make sure that once id's are given, and then the tusclient restarts a session, it will still use the same id's - prepareNetworkForErronousResponses() + prepareNetworkForErronousResponses(testID: mockTestID) let ids = try upload(data: data, amount: 2, customHeaders: [:], shouldSucceed: false) @@ -52,13 +57,15 @@ final class TUSClient_IdsTests: XCTestCase { XCTAssertEqual(ids.count, tusDelegate.failedUploads.count) // Reload client - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate XCTAssert(tusDelegate.startedUploads.isEmpty) - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) for id in ids { try client.retry(id: id) @@ -89,7 +96,7 @@ final class TUSClient_IdsTests: XCTestCase { } func testCorrectIdsAreGivenOnFailure() throws { - prepareNetworkForErronousResponses() + prepareNetworkForErronousResponses(testID: mockTestID) let expectedId = try client.upload(data: Data("hello".utf8)) diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_RetryTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_RetryTests.swift index dba3c184..65483376 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_RetryTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_RetryTests.swift @@ -8,13 +8,19 @@ final class TUSClient_RetryTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! + + private var receivedRequests: [URLRequest] { + MockURLProtocol.receivedRequests(testID: mockTestID) + } override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -23,7 +29,9 @@ final class TUSClient_RetryTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -32,26 +40,27 @@ final class TUSClient_RetryTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } func testClientRetriesOnFailure() throws { - prepareNetworkForErronousResponses() + prepareNetworkForErronousResponses(testID: mockTestID) let fileAmount = 2 try upload(data: data, amount: fileAmount, shouldSucceed: false) let expectedRetryCount = 2 - XCTAssertEqual(fileAmount * (1 + expectedRetryCount), MockURLProtocol.receivedRequests.count) + XCTAssertEqual(fileAmount * (1 + expectedRetryCount), receivedRequests.count) } func testMakeSureMetadataWithTooManyErrorsArentLoadedOnStart() throws { - prepareNetworkForErronousResponses() + prepareNetworkForErronousResponses(testID: mockTestID) // Pre-assertions XCTAssert(tusDelegate.failedUploads.isEmpty) @@ -71,7 +80,9 @@ final class TUSClient_RetryTests: XCTestCase { XCTAssertEqual(uploadCount, tusDelegate.failedUploads.count) // Reload client, and see what happens - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) client.start() XCTAssert(tusDelegate.startedUploads.isEmpty) diff --git a/Tests/TUSKitTests/TUSClient/TUSClient_UploadingTests.swift b/Tests/TUSKitTests/TUSClient/TUSClient_UploadingTests.swift index a464feb9..814333b8 100644 --- a/Tests/TUSKitTests/TUSClient/TUSClient_UploadingTests.swift +++ b/Tests/TUSKitTests/TUSClient/TUSClient_UploadingTests.swift @@ -8,13 +8,19 @@ final class TUSClient_UploadingTests: XCTestCase { var relativeStoragePath: URL! var fullStoragePath: URL! var data: Data! + var mockTestID: String! + + private var receivedRequests: [URLRequest] { + MockURLProtocol.receivedRequests(testID: mockTestID) + } override func setUp() { super.setUp() - relativeStoragePath = URL(string: "TUSTEST")! + relativeStoragePath = URL(string: UUID().uuidString)! + mockTestID = UUID().uuidString - MockURLProtocol.reset() + MockURLProtocol.reset(testID: mockTestID) let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] fullStoragePath = docDir.appendingPathComponent(relativeStoragePath.absoluteString) @@ -23,7 +29,9 @@ final class TUSClient_UploadingTests: XCTestCase { data = Data("abcdef".utf8) - client = makeClient(storagePath: relativeStoragePath) + client = makeClient(storagePath: relativeStoragePath, + sessionIdentifier: "TEST-\(mockTestID!)", + mockTestID: mockTestID) tusDelegate = TUSMockDelegate() client.delegate = tusDelegate do { @@ -32,11 +40,12 @@ final class TUSClient_UploadingTests: XCTestCase { XCTFail("Could not reset \(error)") } - prepareNetworkForSuccesfulUploads(data: data) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) } override func tearDown() { super.tearDown() + MockURLProtocol.reset(testID: mockTestID) clearDirectory(dir: fullStoragePath) } // MARK: - Adding files and data to upload @@ -72,7 +81,7 @@ final class TUSClient_UploadingTests: XCTestCase { func testSmallUploadsArentChunked() throws { let ids = try upload(data: Data("012345678".utf8)) XCTAssertEqual(1, ids.count) - XCTAssertEqual(2, MockURLProtocol.receivedRequests.count) + XCTAssertEqual(2, receivedRequests.count) } func testLargeUploadsWillBeChunked() throws { @@ -82,8 +91,8 @@ final class TUSClient_UploadingTests: XCTestCase { XCTAssert(data.count > Fixtures.chunkSize, "prerequisite failed") let ids = try upload(data: data) XCTAssertEqual(1, ids.count) - XCTAssertEqual(3, MockURLProtocol.receivedRequests.count) - let createRequests = MockURLProtocol.receivedRequests.filter { request in + XCTAssertEqual(3, receivedRequests.count) + let createRequests = receivedRequests.filter { request in request.httpMethod == "POST" } XCTAssertEqual(1, createRequests.count, "The POST method (create) should have been called only once") @@ -91,7 +100,7 @@ final class TUSClient_UploadingTests: XCTestCase { func testClientThrowsErrorsWhenReceivingWrongOffset() throws { // Make sure that if a server gives a "wrong" offset, the uploader errors and doesn't end up in an infinite uploading loop. - prepareNetworkForWrongOffset(data: data) + prepareNetworkForWrongOffset(data: data, testID: mockTestID) try upload(data: data, shouldSucceed: false) XCTAssertEqual(1, tusDelegate.failedUploads.count) } @@ -101,20 +110,20 @@ final class TUSClient_UploadingTests: XCTestCase { // We fail the client first, then restart and make it use a status call to continue // After which we make sure that calls get chunked properly. - prepareNetworkForErronousResponses() + prepareNetworkForErronousResponses(testID: mockTestID) let data = Fixtures.makeLargeData() let ids = try upload(data: data, shouldSucceed: false) // Now that a large upload failed. Let's retry a succesful upload, fetch its status, and check the requests that have been created. - prepareNetworkForSuccesfulUploads(data: data) - resetReceivedRequests() + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) + resetReceivedRequests(testID: mockTestID) try client.retry(id: ids[0]) waitForUploadsToFinish(1) - let creationRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "POST" } - let uploadRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "PATCH" } - let statusReqests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "HEAD" } + let creationRequests = receivedRequests.filter { $0.httpMethod == "POST" } + let uploadRequests = receivedRequests.filter { $0.httpMethod == "PATCH" } + let statusReqests = receivedRequests.filter { $0.httpMethod == "HEAD" } XCTAssert(statusReqests.isEmpty) XCTAssertEqual(1, creationRequests.count) XCTAssertEqual(2, uploadRequests.count) @@ -123,22 +132,22 @@ final class TUSClient_UploadingTests: XCTestCase { func testLargeUploadsWillBeChunkedAfterFetchingStatus() throws { // First we make sure create succeeds. But uploading fails. // This means we can do a status call after. After which we measure if something will get chunked. - prepareNetworkForFailingUploads() + prepareNetworkForFailingUploads(testID: mockTestID) let data = Fixtures.makeLargeData() let ids = try upload(data: data, shouldSucceed: false) // Now a file is created with a remote url. So next fetch means the client will perform a status call. // Let's retry uploading and make sure that status and 2 (not 1, because chunking) calls have been made. - prepareNetworkForSuccesfulStatusCall(data: data) - prepareNetworkForSuccesfulUploads(data: data) - resetReceivedRequests() + prepareNetworkForSuccesfulStatusCall(data: data, testID: mockTestID) + prepareNetworkForSuccesfulUploads(data: data, testID: mockTestID) + resetReceivedRequests(testID: mockTestID) try client.retry(id: ids[0]) waitForUploadsToFinish(1) - let statusReqests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "HEAD" } - let creationRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "POST" } - let uploadRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "PATCH" } + let statusReqests = receivedRequests.filter { $0.httpMethod == "HEAD" } + let creationRequests = receivedRequests.filter { $0.httpMethod == "POST" } + let uploadRequests = receivedRequests.filter { $0.httpMethod == "PATCH" } XCTAssert(creationRequests.isEmpty) XCTAssertEqual(1, statusReqests.count) XCTAssertEqual(2, uploadRequests.count) @@ -147,17 +156,22 @@ final class TUSClient_UploadingTests: XCTestCase { // MARK: - Custom URLs func testUploadingToCustomURL() throws { - let url = URL(string: "www.custom-url")! - try client.upload(data: data, uploadURL: url) + let url = URL(string: "https://www.custom-url")! + let uploadId = try client.upload(data: data, uploadURL: url) waitForUploadsToFinish(1) - let uploadRequests = MockURLProtocol.receivedRequests.filter { $0.httpMethod == "POST" } - XCTAssertEqual(url, uploadRequests.first?.url) + + let expectedFilenameMetadata = "filename \(uploadId.uuidString.toBase64())" + let requestForUpload = receivedRequests.first { request in + request.httpMethod == "POST" && + request.value(forHTTPHeaderField: "Upload-Metadata")?.contains(expectedFilenameMetadata) == true + } + XCTAssertEqual(url, requestForUpload?.url) } // MARK: - Responses func testMakeSureClientCanHandleLowerCaseKeysInResponses() throws { - prepareNetworkForSuccesfulUploads(data: data, lowerCasedKeysInResponses: true) + prepareNetworkForSuccesfulUploads(data: data, lowerCasedKeysInResponses: true, testID: mockTestID) try upload(data: data) }