[api-upload-refactor] Add iOS15 upload progress overload; toObjetct throws

- upload(..., onProgress: @Sendable (Double) -> Void) @available(iOS 15.0, *)
  via URLSession.upload(for:fromFile:delegate:) + UploadProgressDelegate;
  emits 1.0 on completion. Base upload stays iOS 13+
- move all upload code to LCEssentials+API+Upload.swift (buildUploadRequest /
  finishUpload / performUpload shared); API file 400 lines. Shared request
  helpers private -> internal
- Dictionary.toObjetct: try! -> throws
- APIUploadTests: +progress contract test (final 1.0, monotonic, decodes)
This commit is contained in:
Daniel Arantes Loverde
2026-08-29 17:46:41 -03:00
parent 3c64f364be
commit fb2e417104
4 changed files with 233 additions and 85 deletions

View File

@@ -5,6 +5,14 @@ private struct UploadEcho: Decodable, Sendable, Equatable {
let ok: Bool
}
/// Thread-safe sink for progress callbacks (invoked on an arbitrary queue).
private final class ProgressBox: @unchecked Sendable {
private let lock = NSLock()
private var storage: [Double] = []
func record(_ value: Double) { lock.lock(); storage.append(value); lock.unlock() }
var values: [Double] { lock.lock(); defer { lock.unlock() }; return storage }
}
final class APIUploadTests: XCTestCase {
private var api: API!
@@ -79,6 +87,27 @@ final class APIUploadTests: XCTestCase {
XCTAssertEqual(tempCountInDir(dir), before)
}
@available(iOS 15.0, *)
func testProgressOverloadDeliversFinalCompletionAndDecodes() async throws {
StubURLProtocol.setStub(.init(statusCode: 200, body: Data(#"{"ok":true}"#.utf8)))
var form = MultipartForm()
form.file("f", data: Data(repeating: 0x41, count: 4096), filename: "a.bin")
let progressBox = ProgressBox()
let result: UploadEcho = try await api.upload(
url: "https://api.example.com/x",
form: form,
onProgress: { progressBox.record($0) }
)
XCTAssertEqual(result, UploadEcho(ok: true))
let values = progressBox.values
XCTAssertEqual(values.last, 1.0, "final progress must be 1.0")
XCTAssertTrue(values.allSatisfy { $0 >= 0 && $0 <= 1 })
XCTAssertEqual(values, values.sorted(), "progress must be monotonic non-decreasing")
}
func testUploadServerErrorThrowsWithStatus() async {
StubURLProtocol.setStub(.init(statusCode: 413, body: Data(#"{"error":"too big"}"#.utf8)))
var form = MultipartForm()