diff --git a/Sources/TransloaditKit/Transloadit.swift b/Sources/TransloaditKit/Transloadit.swift index a4ff4c1..c2189c7 100644 --- a/Sources/TransloaditKit/Transloadit.swift +++ b/Sources/TransloaditKit/Transloadit.swift @@ -53,14 +53,16 @@ public final class Transloadit { typealias FileId = UUID var pollers = [[URL]: TransloaditPoller]() - private let api: TransloaditAPI + // "private" -- only exposed for unit testing + let api: TransloaditAPI private let storageDir: URL? public var remainingUploads: Int { tusClient.remainingUploads } - private let tusSessionConfig: URLSessionConfiguration + // "private" -- only exposed for unit testing + let tusSessionConfig: URLSessionConfiguration lazy var tusClient: TUSClient = { let tusClient = try! TUSClient(server: URL(string:"https://www.transloadit.com")!, sessionIdentifier: "TransloadIt", sessionConfiguration: tusSessionConfig, storageDirectory: storageDir) tusClient.delegate = self diff --git a/Sources/TransloaditKit/TransloaditAPI.swift b/Sources/TransloaditKit/TransloaditAPI.swift index 6c33c2a..a4ea31d 100644 --- a/Sources/TransloaditKit/TransloaditAPI.swift +++ b/Sources/TransloaditKit/TransloaditAPI.swift @@ -26,7 +26,7 @@ final class TransloaditAPI: NSObject { case assemblies = "/assemblies" } - private let configuration: URLSessionConfiguration + let configuration: URLSessionConfiguration private let delegateQueue: OperationQueue? private lazy var session: URLSession = { return URLSession(configuration: configuration, delegate: self, delegateQueue: delegateQueue) diff --git a/Tests/TransloaditKitTests/Fixtures.swift b/Tests/TransloaditKitTests/Helpers/Fixtures.swift similarity index 100% rename from Tests/TransloaditKitTests/Fixtures.swift rename to Tests/TransloaditKitTests/Helpers/Fixtures.swift diff --git a/Tests/TransloaditKitTests/MockURLProtocol.swift b/Tests/TransloaditKitTests/Helpers/MockURLProtocol.swift similarity index 100% rename from Tests/TransloaditKitTests/MockURLProtocol.swift rename to Tests/TransloaditKitTests/Helpers/MockURLProtocol.swift diff --git a/Tests/TransloaditKitTests/Support.swift b/Tests/TransloaditKitTests/Helpers/Support.swift similarity index 100% rename from Tests/TransloaditKitTests/Support.swift rename to Tests/TransloaditKitTests/Helpers/Support.swift diff --git a/Tests/TransloaditKitTests/TransloadItMockDelegate.swift b/Tests/TransloaditKitTests/Helpers/TransloadItMockDelegate.swift similarity index 100% rename from Tests/TransloaditKitTests/TransloadItMockDelegate.swift rename to Tests/TransloaditKitTests/Helpers/TransloadItMockDelegate.swift diff --git a/Tests/TransloaditKitTests/SessionCopyTests.swift b/Tests/TransloaditKitTests/SessionCopyTests.swift new file mode 100644 index 0000000..cddc9a9 --- /dev/null +++ b/Tests/TransloaditKitTests/SessionCopyTests.swift @@ -0,0 +1,78 @@ +// +// SessionCopyTests.swift +// TransloaditKit +// +// Created by Donny Wals on 28/11/2024. +// + +import Testing +import Foundation +@testable import TransloaditKit + +let expectedTUSClientConfigIdentifier = "com.transloadit.tus.bg" +let expectedTransloaditConfigIdentifier = "com.transloadit.bg" +let transloaditConfigIdentifierForTesting = "com.transloadit.bg1" + +@Test("Default session should not use an identifier when copying") +func defaultSessionIgnoresIdentifierWhenCopyingSession() async throws { + let session = URLSessionConfiguration.default + #expect(session.identifier == nil) + let copy = session.copy(withIdentifier: "testIdentifier") + #expect(copy.identifier == nil) +} + +@Test("Background session should use an identifier when copying") +func backgroundSessionUsesIdentifierWhenCopyingSession() async throws { + let session = URLSessionConfiguration.background(withIdentifier: transloaditConfigIdentifierForTesting) + #expect(session.identifier == transloaditConfigIdentifierForTesting) + let copy = session.copy(withIdentifier: "com.transloadit.bg2") + #expect(copy.identifier == "com.transloadit.bg2") +} + +@Test("TransloaditKit should use provided configuration") +func transloaditKitShouldUseProvidedConfig() async throws { + let config = URLSessionConfiguration.background(withIdentifier: transloaditConfigIdentifierForTesting) + let transloadit = Transloadit( + credentials: .init(key: "", secret: ""), + sessionConfiguration: config) + #expect(transloadit.api.configuration.identifier == transloaditConfigIdentifierForTesting) +} + +@Test("TransloaditKit should make config copy when given a background URLSession") +func transloaditKitShouldMakeConfigCopyForBackgroundURLSession() async throws { + let config = URLSessionConfiguration.background(withIdentifier: transloaditConfigIdentifierForTesting) + let session = URLSession(configuration: config) + let transloadit = Transloadit( + credentials: .init(key: "", secret: ""), + session: session) + #expect(transloadit.api.configuration.identifier == expectedTransloaditConfigIdentifier) +} + +@Test("TUSClient should be given its own background configuration") +func tusClientShouldMakeSessionCopy() async throws { + let config = URLSessionConfiguration.background(withIdentifier: transloaditConfigIdentifierForTesting) + let transloadit = Transloadit( + credentials: .init(key: "", secret: ""), + sessionConfiguration: config) + #expect(transloadit.tusSessionConfig.identifier == expectedTUSClientConfigIdentifier) +} + +@Test("TUSClient and TransloaditKit should have unique session configuration identifiers when providing a config") +func tusAndTransloaditHaveUniqueIdentifiersWhenProvidingConfiguration() async throws { + let config = URLSessionConfiguration.background(withIdentifier: transloaditConfigIdentifierForTesting) + let transloadit = Transloadit( + credentials: .init(key: "", secret: ""), + sessionConfiguration: config) + #expect(transloadit.tusSessionConfig.identifier == expectedTUSClientConfigIdentifier) + #expect(transloadit.api.configuration.identifier == transloaditConfigIdentifierForTesting) +} + +@Test("TUSClient and TransloaditKit should have unique session configuration identifiers when providing a session") +func tusAndTransloaditHaveUniqueIdentifiersWhenProvidingSession() async throws { + let config = URLSessionConfiguration.background(withIdentifier: transloaditConfigIdentifierForTesting) + let transloadit = Transloadit( + credentials: .init(key: "", secret: ""), + session: URLSession(configuration: config)) + #expect(transloadit.tusSessionConfig.identifier == expectedTUSClientConfigIdentifier) + #expect(transloadit.api.configuration.identifier == expectedTransloaditConfigIdentifier) +} diff --git a/Tests/TransloaditKitTests/TransloaditKitTests.swift b/Tests/TransloaditKitTests/TransloaditKitTests.swift index 46be0fe..601ec1a 100644 --- a/Tests/TransloaditKitTests/TransloaditKitTests.swift +++ b/Tests/TransloaditKitTests/TransloaditKitTests.swift @@ -2,7 +2,7 @@ import XCTest import TransloaditKit // ⚠️ WARNING: We are not performing a testable import here. We want to test the real public API. By doing so, we'll know very quicklly if the public API is broken. Which is very important to prevent. import AVFoundation -final class TransloaditKitTests: XCTestCase { +class TransloaditKitTests: XCTestCase { public var transloadit: Transloadit! let resizeStep = Step(name: "resize", robot: "/image/resize", options: ["width": 50, @@ -32,7 +32,7 @@ final class TransloaditKitTests: XCTestCase { transloadit.fileDelegate = nil } - private func makeClient() -> Transloadit { + fileprivate func makeClient() -> Transloadit { let credentials = Transloadit.Credentials(key: "I am a key", secret: "I am a secret") let configuration = URLSessionConfiguration.default diff --git a/TransloaditKitExample/TransloaditKitExample/ContentView.swift b/TransloaditKitExample/TransloaditKitExample/ContentView.swift index 5c14632..910f2a5 100644 --- a/TransloaditKitExample/TransloaditKitExample/ContentView.swift +++ b/TransloaditKitExample/TransloaditKitExample/ContentView.swift @@ -10,7 +10,13 @@ import SwiftUI struct ContentView: View { @ObservedObject var uploader: MyUploader + @ObservedObject var backgroundUploader: MyUploader @State private var showingImagePicker = false + @State var uploadUsingBackgroundConfig = false + + var currentUploader: MyUploader { + uploadUsingBackgroundConfig ? backgroundUploader : uploader + } var body: some View { VStack { @@ -21,11 +27,27 @@ struct ContentView: View { Button("Select image(s)") { showingImagePicker.toggle() }.sheet(isPresented:$showingImagePicker, content: { - PhotoPicker { [weak uploader] urls in + PhotoPicker { [weak uploader, weak backgroundUploader] urls in print(urls) - uploader?.upload(urls) + if uploadUsingBackgroundConfig { + backgroundUploader?.upload(urls) + } else { + uploader?.upload(urls) + } } }) + + Toggle(isOn: $uploadUsingBackgroundConfig, label: { + Text("Upload using background session") + }) + .padding(.vertical, 8) + + if currentUploader.progress > 0.0 && !currentUploader.uploadCompleted { + Text("Upload progress") + ProgressView(value: currentUploader.progress, total: 1.0) + } else if currentUploader.uploadCompleted { + Text("File uploaded 🟢") + } } } } @@ -33,6 +55,7 @@ struct ContentView: View { struct ContentView_Previews: PreviewProvider { static var previews: some View { let uploader = MyUploader() - ContentView(uploader: uploader) + let bgUploader = MyUploader(backgroundUploader: true) + ContentView(uploader: uploader, backgroundUploader: bgUploader) } } diff --git a/TransloaditKitExample/TransloaditKitExample/TransloaditKitExampleApp.swift b/TransloaditKitExample/TransloaditKitExample/TransloaditKitExampleApp.swift index c48f9ad..1f90d05 100644 --- a/TransloaditKitExample/TransloaditKitExample/TransloaditKitExampleApp.swift +++ b/TransloaditKitExample/TransloaditKitExample/TransloaditKitExampleApp.swift @@ -10,8 +10,22 @@ import TransloaditKit import Atlantis final class MyUploader: ObservableObject { + @Published var progress: Float = 0.0 + @Published var uploadCompleted = false + let transloadit: Transloadit + init(backgroundUploader: Bool = false) { + let credentials = Transloadit.Credentials(key: "OsCOAe4ro8CyNsHTp8pdhSiyEzuqwBue", secret: "jB5gZqmkiu2sdSwc7pko8iajD9ailws1eYUtwoKj") + + if backgroundUploader { + self.transloadit = Transloadit(credentials: credentials, sessionConfiguration: .background(withIdentifier: "com.transloadit.bg_sample")) + } else { + self.transloadit = Transloadit(credentials: credentials, sessionConfiguration: .default) + } + self.transloadit.fileDelegate = self + } + func upload2(_ urls: [URL]) { let templateID = "1a84d2f1f2584f92981bda285bbc4e84" @@ -50,14 +64,6 @@ final class MyUploader: ObservableObject { } } } - - init() { - let credentials = Transloadit.Credentials(key: "", secret: "") - self.transloadit = Transloadit(credentials: credentials, sessionConfiguration: .default) - //self.transloadit = Transloadit(credentials: credentials, sessionConfiguration: .background(withIdentifier: "com.transloadit.bg_sample")) - self.transloadit.fileDelegate = self - } - } enum StepFactory { @@ -73,7 +79,9 @@ enum StepFactory { extension MyUploader: TransloaditFileDelegate { func progressFor(assembly: Assembly, bytesUploaded: Int, totalBytes: Int, client: Transloadit) { print("Progress for \(assembly) is \(bytesUploaded) / \(totalBytes)") - + Task { @MainActor in + progress = Float(bytesUploaded) / Float(totalBytes) + } } func totalProgress(bytesUploaded: Int, totalBytes: Int, client: Transloadit) { @@ -94,6 +102,10 @@ extension MyUploader: TransloaditFileDelegate { func didFinishUpload(assembly: Assembly, client: Transloadit) { print("didFinishUpload") + Task { @MainActor in + progress = 1.0 + uploadCompleted = true + } transloadit.fetchStatus(assemblyURL: assembly.url) { result in print("status result \(result)") @@ -102,21 +114,25 @@ extension MyUploader: TransloaditFileDelegate { func didStartUpload(assembly: Assembly, client: Transloadit) { print("didStartUpload") + Task { @MainActor in + progress = 0.0 + uploadCompleted = false + } } } @main struct TransloaditKitExampleApp: App { - @ObservedObject var uploader: MyUploader + @StateObject var uploader = MyUploader() + @StateObject var backgroundUploader = MyUploader(backgroundUploader: true) init() { - self.uploader = MyUploader() Atlantis.start(hostName: "donnys-macbook-pro-2.local.") } var body: some Scene { WindowGroup { - ContentView(uploader: uploader) + ContentView(uploader: uploader, backgroundUploader: backgroundUploader) } } }