96 lines
3.4 KiB
Swift
96 lines
3.4 KiB
Swift
|
|
import XCTest
|
||
|
|
@testable import LCEssentials
|
||
|
|
|
||
|
|
private struct UploadEcho: Decodable, Sendable, Equatable {
|
||
|
|
let ok: Bool
|
||
|
|
}
|
||
|
|
|
||
|
|
final class APIUploadTests: XCTestCase {
|
||
|
|
|
||
|
|
private var api: API!
|
||
|
|
|
||
|
|
override func setUp() {
|
||
|
|
super.setUp()
|
||
|
|
let cfg = URLSessionConfiguration.ephemeral
|
||
|
|
cfg.protocolClasses = [StubURLProtocol.self]
|
||
|
|
api = API(testConfiguration: cfg)
|
||
|
|
}
|
||
|
|
|
||
|
|
override func tearDown() {
|
||
|
|
StubURLProtocol.reset()
|
||
|
|
api = nil
|
||
|
|
super.tearDown()
|
||
|
|
}
|
||
|
|
|
||
|
|
private func tempCountInDir(_ dir: URL) -> Int {
|
||
|
|
(try? FileManager.default.contentsOfDirectory(atPath: dir.path).filter {
|
||
|
|
$0.hasPrefix("lce-multipart-")
|
||
|
|
}.count) ?? -1
|
||
|
|
}
|
||
|
|
|
||
|
|
func testUploadSendsMultipartBodyAndDecodesResponse() async throws {
|
||
|
|
StubURLProtocol.setStub(.init(statusCode: 200, body: Data(#"{"ok":true}"#.utf8)))
|
||
|
|
|
||
|
|
var form = MultipartForm()
|
||
|
|
form.field("caption", "hi")
|
||
|
|
form.file("photo", data: Data([0x89, 0x50, 0x4E, 0x47]), filename: "p.png")
|
||
|
|
let expectedBody = try Data(contentsOf: form.serialize().fileURL)
|
||
|
|
|
||
|
|
let result: UploadEcho = try await api.upload(
|
||
|
|
url: "https://api.example.com/media",
|
||
|
|
form: form
|
||
|
|
)
|
||
|
|
|
||
|
|
XCTAssertEqual(result, UploadEcho(ok: true))
|
||
|
|
let sent = StubURLProtocol.capturedRequests.first
|
||
|
|
XCTAssertEqual(sent?.httpMethod, "POST")
|
||
|
|
XCTAssertTrue(sent?.value(forHTTPHeaderField: "Content-Type")?
|
||
|
|
.hasPrefix("multipart/form-data; boundary=") ?? false)
|
||
|
|
// body framing matches a fresh serialize() (boundary differs per form
|
||
|
|
// instance, so compare structure, not bytes, by re-serialising the SAME form)
|
||
|
|
XCTAssertEqual(StubURLProtocol.lastCapturedBody?.count, expectedBody.count)
|
||
|
|
}
|
||
|
|
|
||
|
|
func testTempBodyFileRemovedAfterSuccess() async throws {
|
||
|
|
StubURLProtocol.setStub(.init(statusCode: 200, body: Data(#"{"ok":true}"#.utf8)))
|
||
|
|
let dir = FileManager.default.temporaryDirectory
|
||
|
|
let before = tempCountInDir(dir)
|
||
|
|
|
||
|
|
var form = MultipartForm()
|
||
|
|
form.file("f", data: Data("x".utf8), filename: "a.txt")
|
||
|
|
let _: UploadEcho = try await api.upload(url: "https://api.example.com/x", form: form)
|
||
|
|
|
||
|
|
XCTAssertEqual(tempCountInDir(dir), before)
|
||
|
|
}
|
||
|
|
|
||
|
|
func testTempBodyFileRemovedAfterThrow() async {
|
||
|
|
StubURLProtocol.setStub(.init(statusCode: 500, body: Data(#"{"error":"boom"}"#.utf8)))
|
||
|
|
let dir = FileManager.default.temporaryDirectory
|
||
|
|
let before = tempCountInDir(dir)
|
||
|
|
|
||
|
|
var form = MultipartForm()
|
||
|
|
form.file("f", data: Data("x".utf8), filename: "a.txt")
|
||
|
|
do {
|
||
|
|
let _: UploadEcho = try await api.upload(url: "https://api.example.com/x", form: form)
|
||
|
|
XCTFail("expected throw")
|
||
|
|
} catch {
|
||
|
|
// expected
|
||
|
|
}
|
||
|
|
XCTAssertEqual(tempCountInDir(dir), before)
|
||
|
|
}
|
||
|
|
|
||
|
|
func testUploadServerErrorThrowsWithStatus() async {
|
||
|
|
StubURLProtocol.setStub(.init(statusCode: 413, body: Data(#"{"error":"too big"}"#.utf8)))
|
||
|
|
var form = MultipartForm()
|
||
|
|
form.file("f", data: Data("x".utf8), filename: "a.txt")
|
||
|
|
do {
|
||
|
|
let _: UploadEcho = try await api.upload(url: "https://api.example.com/x", form: form)
|
||
|
|
XCTFail("expected throw")
|
||
|
|
} catch let error as NSError {
|
||
|
|
XCTAssertEqual(error.code, 413)
|
||
|
|
} catch {
|
||
|
|
XCTFail("wrong error: \(error)")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|