import XCTest @testable import LCEssentials final class MultipartFormTests: XCTestCase { private func readSerialized(_ form: MultipartForm) throws -> (body: Data, contentType: String, url: URL) { let result = try form.serialize() let data = try Data(contentsOf: result.fileURL) return (data, result.contentType, result.fileURL) } private func addCleanup(_ url: URL) { addTeardownBlock { try? FileManager.default.removeItem(at: url) } } // MARK: - Fields func testFieldPartHasNoContentTypeLine() throws { var form = MultipartForm() form.field("caption", "hello world") let (body, contentType, url) = try readSerialized(form) addCleanup(url) let text = String(data: body, encoding: .utf8)! XCTAssertTrue(contentType.hasPrefix("multipart/form-data; boundary=")) let boundary = String(contentType.split(separator: "=", maxSplits: 1)[1]) XCTAssertEqual(text, [ "--\(boundary)\r\n", "Content-Disposition: form-data; name=\"caption\"\r\n", "\r\n", "hello world\r\n", "--\(boundary)--\r\n" ].joined()) } // MARK: - In-memory data file func testDataFilePartCarriesFilenameAndMimeAndRawBytes() throws { let png = Data([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0xFF]) var form = MultipartForm() form.file("photo", data: png, filename: "p.png") let (body, contentType, url) = try readSerialized(form) addCleanup(url) let boundary = String(contentType.split(separator: "=", maxSplits: 1)[1]) let text = String(data: body, encoding: .isoLatin1)! XCTAssertTrue(text.contains("Content-Disposition: form-data; name=\"photo\"; filename=\"p.png\"\r\n")) XCTAssertTrue(text.contains("Content-Type: image/png\r\n")) XCTAssertTrue(text.hasSuffix("--\(boundary)--\r\n")) // raw bytes appear verbatim between the blank line and the trailing CRLF let marker = Data("\r\n\r\n".utf8) let range = body.range(of: marker)! let afterHeader = body[range.upperBound...] XCTAssertTrue(afterHeader.starts(with: png)) } func testExplicitMimeOverridesGuess() throws { var form = MultipartForm() form.file("f", data: Data("x".utf8), filename: "a.png", mime: "application/octet-stream") let (body, _, url) = try readSerialized(form) addCleanup(url) XCTAssertTrue(String(data: body, encoding: .utf8)!.contains("Content-Type: application/octet-stream\r\n")) } // MARK: - Disk file, streamed func testDiskFilePartIsStreamedAndContentMatches() throws { let big = Data((0..<(2 * 1024 * 1024)).map { UInt8($0 % 251) }) let src = FileManager.default.temporaryDirectory.appendingPathComponent("src-\(UUID().uuidString).bin") try big.write(to: src) addCleanup(src) var form = MultipartForm() form.field("kind", "raw") form.file("doc", url: src) let (body, contentType, out) = try readSerialized(form) addCleanup(out) let boundary = String(contentType.split(separator: "=", maxSplits: 1)[1]) // header uses the source filename XCTAssertTrue(String(data: body.prefix(400), encoding: .isoLatin1)! .contains("filename=\"\(src.lastPathComponent)\"")) // the 2 MB payload is present verbatim let marker = Data("Content-Type: application/octet-stream\r\n\r\n".utf8) let r = body.range(of: marker)! let payload = body[r.upperBound..<(body.index(r.upperBound, offsetBy: big.count))] XCTAssertEqual(Data(payload), big) XCTAssertTrue(String(data: body.suffix(boundary.count + 8), encoding: .utf8)! .hasSuffix("--\(boundary)--\r\n")) } // MARK: - MIME lookup func testMimeTypeLookup() { XCTAssertEqual(MultipartForm.mimeType(for: "a.jpg"), "image/jpeg") XCTAssertEqual(MultipartForm.mimeType(for: "a.PDF"), "application/pdf") XCTAssertEqual(MultipartForm.mimeType(for: "a.unknownext"), "application/octet-stream") } }