Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Next

* Added `Transloadit-Client` headers with the SDK name and version to assembly creation, status, and cancellation requests.
* Added `tusUploadChunkSize` to Transloadit initializers so apps can tune TUS chunking behavior, including single-request uploads with `0`.
* Clarified that `createAssembly(... andUpload ..., completion:)` completes after upload scheduling, not after file upload or processing completion.

Expand Down
10 changes: 9 additions & 1 deletion Sources/TransloaditKit/TransloaditAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ enum TransloaditAPIError: Error {
final class TransloaditAPI: NSObject {

private let basePath = URL(string: "https://api2.transloadit.com")!

// Keep this version in sync with Transloadit.podspec when releasing; header tests enforce it.
private static let clientHeader = "transloaditkit:3.5.0"

enum Endpoint: String {
case assemblies = "/assemblies"
Expand Down Expand Up @@ -180,7 +183,10 @@ final class TransloaditAPI: NSObject {
let path = basePath.appendingPathComponent(Endpoint.assemblies.rawValue)
var request: URLRequest = URLRequest(url: path, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30)

let headers = ["Content-Type": String(format: "multipart/form-data; boundary=%@", boundary)]
let headers = [
"Content-Type": String(format: "multipart/form-data; boundary=%@", boundary),
"Transloadit-Client": Self.clientHeader
]

request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
Expand Down Expand Up @@ -281,6 +287,7 @@ final class TransloaditAPI: NSObject {
func makeRequest() -> URLRequest {
var request = URLRequest(url: assemblyURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
request.httpMethod = "GET"
request.setValue(Self.clientHeader, forHTTPHeaderField: "Transloadit-Client")
return request
}

Expand All @@ -307,6 +314,7 @@ final class TransloaditAPI: NSObject {
func makeRequest() -> URLRequest {
var request = URLRequest(url: assembly.url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
request.httpMethod = "DELETE"
request.setValue(Self.clientHeader, forHTTPHeaderField: "Transloadit-Client")
return request
}

Expand Down
122 changes: 122 additions & 0 deletions Tests/TransloaditKitTests/TransloaditAPIHeaderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import Foundation
import XCTest
@testable import TransloaditKit

final class TransloaditAPIHeaderTests: XCTestCase {
private var api: TransloaditAPI!
private var expectedClientHeader = ""

override func setUpWithError() throws {
try super.setUpWithError()
// Check the transmitted version against release metadata to catch version drift.
let repoURL = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
let podspec = try String(contentsOf: repoURL.appendingPathComponent("Transloadit.podspec"), encoding: .utf8)
let versionPattern = try NSRegularExpression(pattern: #"s\.version\s*=\s*['"]([^'"]+)['"]"#)
let versionMatch = try XCTUnwrap(
versionPattern.firstMatch(in: podspec, range: NSRange(location: 0, length: podspec.utf16.count)),
"Transloadit.podspec must declare an SDK version"
)
let versionRange = try XCTUnwrap(Range(versionMatch.range(at: 1), in: podspec))
expectedClientHeader = "transloaditkit:\(podspec[versionRange])"
MockURLProtocol.reset()

let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
configuration.httpAdditionalHeaders = ["X-Test-Header": "preserved"]
api = TransloaditAPI(
credentials: .init(key: "test-key", secret: nil),
sessionConfiguration: configuration,
signatureGenerator: { _, completion in
completion(.success("test-signature"))
}
)
}

override func tearDown() {
api = nil
MockURLProtocol.reset()
super.tearDown()
}

func testCreateAssemblySendsClientHeader() {
let assembly = Fixtures.makeAssembly()
prepareResponse(
for: URL(string: "https://api2.transloadit.com/assemblies")!,
method: "POST",
data: Fixtures.makeAssemblyResponse(assembly: assembly)
)
let completed = expectation(description: "Assembly created")

api.createAssembly(steps: [], expectedNumberOfFiles: 0, customFields: [:]) { result in
switch result {
case .success(let receivedAssembly):
XCTAssertEqual(receivedAssembly, assembly)
case .failure(let error):
XCTFail("Creating an assembly failed: \(error)")
}
completed.fulfill()
}

wait(for: [completed], timeout: 3)
}

func testFetchStatusSendsClientHeader() {
let assembly = Fixtures.makeAssembly()
prepareResponse(
for: assembly.url,
method: "GET",
data: Fixtures.makeAssemblyStatusResponse(assemblyStatus: Fixtures.makeAssemblyStatus(status: .completed))
)
let completed = expectation(description: "Status fetched")

api.fetchStatus(assemblyURL: assembly.url) { result in
switch result {
case .success(let status):
XCTAssertEqual(status.processingStatus, .completed)
case .failure(let error):
XCTFail("Fetching status failed: \(error)")
}
completed.fulfill()
}

wait(for: [completed], timeout: 3)
}

func testCancelAssemblySendsClientHeader() {
let assembly = Fixtures.makeAssembly()
prepareResponse(
for: assembly.url,
method: "DELETE",
data: Fixtures.makeAssemblyStatusResponse(assemblyStatus: Fixtures.makeAssemblyStatus(status: .canceled))
)
let completed = expectation(description: "Assembly canceled")

api.cancelAssembly(assembly) { result in
switch result {
case .success(let status):
XCTAssertEqual(status.processingStatus, .canceled)
case .failure(let error):
XCTFail("Canceling an assembly failed: \(error)")
}
completed.fulfill()
}

wait(for: [completed], timeout: 3)
}

private func prepareResponse(for url: URL, method: String, data: Data) {
let expectedClientHeader = self.expectedClientHeader
MockURLProtocol.prepareResponse(for: url, method: method) { headers in
XCTAssertEqual(headers?["Transloadit-Client"], expectedClientHeader)
// Session headers should survive adding SDK request headers on supported Apple platforms.
XCTAssertEqual(headers?["X-Test-Header"], "preserved")
if method == "POST" {
XCTAssertTrue(headers?["Content-Type"]?.hasPrefix("multipart/form-data; boundary=") == true)
}
return MockURLProtocol.Response(status: 200, headers: [:], data: data)
}
}
}