diff --git a/.gitignore b/.gitignore index d8d32c87..ae8e14c9 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ Carthage/Build fastlane/report.xml fastlane/screenshots + +.DS_Store \ No newline at end of file diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index 8fc0f748..256ebe51 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 0ED17E891E229EC700EC1114 /* QueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E881E229EC700EC1114 /* QueryParameters.swift */; }; + 0ED17E8B1E22A22900EC1114 /* URLEncodedQueryParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */; }; + 0ED17E8E1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */; }; 0969AE0F259DEC6D00C498AF /* Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0969AE0E259DEC6D00C498AF /* Combine.swift */; }; 0973EE35259E2DDC00879BA2 /* CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0973EE34259E2DDC00879BA2 /* CombineTests.swift */; }; 7F698E501D9D680C00F1561D /* FormURLEncodedBodyParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F698E3C1D9D680C00F1561D /* FormURLEncodedBodyParametersTests.swift */; }; @@ -78,6 +81,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0ED17E881E229EC700EC1114 /* QueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryParameters.swift; sourceTree = ""; }; + 0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParameters.swift; sourceTree = ""; }; + 0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedQueryParametersTests.swift; sourceTree = ""; }; 0969AE0E259DEC6D00C498AF /* Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Combine.swift; sourceTree = ""; }; 0973EE34259E2DDC00879BA2 /* CombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineTests.swift; sourceTree = ""; }; 141F120F1C1C96820026D415 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Base.xcconfig; path = Configurations/Base.xcconfig; sourceTree = ""; }; @@ -153,6 +159,24 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0ED17E871E229EAC00EC1114 /* QueryParameters */ = { + isa = PBXGroup; + children = ( + 0ED17E881E229EC700EC1114 /* QueryParameters.swift */, + 0ED17E8A1E22A22900EC1114 /* URLEncodedQueryParameters.swift */, + ); + name = QueryParameters; + path = APIKit/QueryParameters; + sourceTree = ""; + }; + 0ED17E8C1E22AC5D00EC1114 /* QueryParameters */ = { + isa = PBXGroup; + children = ( + 0ED17E8D1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift */, + ); + path = QueryParameters; + sourceTree = ""; + }; 0969AE0D259DEC3C00C498AF /* Combine */ = { isa = PBXGroup; children = ( @@ -240,6 +264,7 @@ 7F698E451D9D680C00F1561D /* RequestTests.swift */, 7F698E491D9D680C00F1561D /* SessionCallbackQueueTests.swift */, 7F698E4A1D9D680C00F1561D /* SessionTests.swift */, + 0ED17E8C1E22AC5D00EC1114 /* QueryParameters */, 0973EE33259E2DD000879BA2 /* Combine */, 7F698E3B1D9D680C00F1561D /* BodyParametersType */, 7F698E401D9D680C00F1561D /* DataParserType */, @@ -312,6 +337,7 @@ 7F7048CC1D9D89BE003C99F6 /* Unavailable.swift */, 0969AE0D259DEC3C00C498AF /* Combine */, 7F85FB8B1C9D317300CEE132 /* SessionAdapter */, + 0ED17E871E229EAC00EC1114 /* QueryParameters */, 7F18BD0D1C972C38003A31DF /* BodyParameters */, 7FA19A441C9CC9A2005D25AE /* DataParser */, 7F18BD161C9730ED003A31DF /* Serializations */, @@ -477,12 +503,14 @@ files = ( 7F7048D31D9D89BE003C99F6 /* Unavailable.swift in Sources */, 7F7048D11D9D89BE003C99F6 /* Request.swift in Sources */, + 0ED17E891E229EC700EC1114 /* QueryParameters.swift in Sources */, 7F7048E81D9D8A08003C99F6 /* DataParser.swift in Sources */, 7F7048CE1D9D89BE003C99F6 /* CallbackQueue.swift in Sources */, 7F7048DE1D9D89FB003C99F6 /* AbstractInputStream.m in Sources */, 7F7048E31D9D89FB003C99F6 /* MultipartFormDataBodyParameters.swift in Sources */, 7F7048F01D9D8A12003C99F6 /* ResponseError.swift in Sources */, 7F7048EA1D9D8A08003C99F6 /* JSONDataParser.swift in Sources */, + 0ED17E8B1E22A22900EC1114 /* URLEncodedQueryParameters.swift in Sources */, 7F7048D21D9D89BE003C99F6 /* Session.swift in Sources */, 7F7048E01D9D89FB003C99F6 /* Data+InputStream.swift in Sources */, 7F7048DF1D9D89FB003C99F6 /* BodyParameters.swift in Sources */, @@ -512,6 +540,7 @@ 7F698E501D9D680C00F1561D /* FormURLEncodedBodyParametersTests.swift in Sources */, 7F698E581D9D680C00F1561D /* RequestTests.swift in Sources */, ECA8314A1DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift in Sources */, + 0ED17E8E1E22AC8300EC1114 /* URLEncodedQueryParametersTests.swift in Sources */, 7F698E5E1D9D680C00F1561D /* TestRequest.swift in Sources */, 7F698E601D9D680C00F1561D /* TestSessionTask.swift in Sources */, 0973EE35259E2DDC00879BA2 /* CombineTests.swift in Sources */, diff --git a/Sources/APIKit/DataParser/DataParser.swift b/Sources/APIKit/DataParser/DataParser.swift index 304db092..7e0ffbc3 100644 --- a/Sources/APIKit/DataParser/DataParser.swift +++ b/Sources/APIKit/DataParser/DataParser.swift @@ -2,10 +2,12 @@ import Foundation /// `DataParser` protocol provides interface to parse HTTP response body and to state Content-Type to accept. public protocol DataParser { + associatedtype Parsed + /// Value for `Accept` header field of HTTP request. var contentType: String? { get } /// Return `Any` that expresses structure of response such as JSON and XML. /// - Throws: `Error` when parser encountered invalid format data. - func parse(data: Data) throws -> Any + func parse(data: Data) throws -> Parsed } diff --git a/Sources/APIKit/QueryParameters/QueryParameters.swift b/Sources/APIKit/QueryParameters/QueryParameters.swift new file mode 100644 index 00000000..6e9f16a5 --- /dev/null +++ b/Sources/APIKit/QueryParameters/QueryParameters.swift @@ -0,0 +1,7 @@ +import Foundation + +/// `QueryParameters` provides interface to generate HTTP URL query strings. +public protocol QueryParameters { + /// Generate URL query strings. + func encode() -> String? +} diff --git a/Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift b/Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift new file mode 100644 index 00000000..e4c0f476 --- /dev/null +++ b/Sources/APIKit/QueryParameters/URLEncodedQueryParameters.swift @@ -0,0 +1,20 @@ +import Foundation + +/// `URLEncodedQueryParameters` serializes form object for HTTP URL query. +public struct URLEncodedQueryParameters: QueryParameters { + /// The parameters to be url encoded. + public let parameters: Any + + /// Returns `URLEncodedQueryParameters` that is initialized with parameters. + public init(parameters: Any) { + self.parameters = parameters + } + + /// Generate url encoded `String`. + public func encode() -> String? { + guard let parameters = parameters as? [String: Any], !parameters.isEmpty else { + return nil + } + return URLEncodedSerialization.string(from: parameters) + } +} diff --git a/Sources/APIKit/Request.swift b/Sources/APIKit/Request.swift index efe68c97..115d75c8 100644 --- a/Sources/APIKit/Request.swift +++ b/Sources/APIKit/Request.swift @@ -10,6 +10,7 @@ import Foundation public protocol Request { /// The response type associated with the request type. associatedtype Response + associatedtype DataParser: APIKit.DataParser /// The base URL. var baseURL: URL { get } @@ -28,7 +29,7 @@ public protocol Request { /// The actual parameters for the URL query. The values of this property will be escaped using `URLEncodedSerialization`. /// If this property is not implemented and `method.prefersQueryParameter` is `true`, the value of this property /// will be computed from `parameters`. - var queryParameters: [String: Any]? { get } + var queryParameters: QueryParameters? { get } /// The actual parameters for the HTTP body. If this property is not implemented and `method.prefersQueryParameter` is `false`, /// the value of this property will be computed from `parameters` using `JSONBodyParameters`. @@ -52,12 +53,12 @@ public protocol Request { /// The default implementation of this method is provided to throw `ResponseError.unacceptableStatusCode` /// if the HTTP status code is not in `200..<300`. /// - Throws: `Error` - func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any + func intercept(object: DataParser.Parsed, urlResponse: HTTPURLResponse) throws -> DataParser.Parsed /// Build `Response` instance from raw response object. This method is called after /// `intercept(object:urlResponse:)` if it does not throw any error. /// - Throws: `Error` - func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response + func response(from object: DataParser.Parsed, urlResponse: HTTPURLResponse) throws -> Response } public extension Request { @@ -65,12 +66,12 @@ public extension Request { return nil } - var queryParameters: [String: Any]? { - guard let parameters = parameters as? [String: Any], method.prefersQueryParameters else { + var queryParameters: QueryParameters? { + guard let parameters = parameters, method.prefersQueryParameters else { return nil } - return parameters + return URLEncodedQueryParameters(parameters: parameters) } var bodyParameters: BodyParameters? { @@ -85,15 +86,11 @@ public extension Request { return [:] } - var dataParser: DataParser { - return JSONDataParser(readingOptions: []) - } - func intercept(urlRequest: URLRequest) throws -> URLRequest { return urlRequest } - func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any { + func intercept(object: DataParser.Parsed, urlResponse: HTTPURLResponse) throws -> DataParser.Parsed { guard 200..<300 ~= urlResponse.statusCode else { throw ResponseError.unacceptableStatusCode(urlResponse.statusCode) } @@ -110,8 +107,8 @@ public extension Request { var urlRequest = URLRequest(url: url) - if let queryParameters = queryParameters, !queryParameters.isEmpty { - components.percentEncodedQuery = URLEncodedSerialization.string(from: queryParameters) + if let queryString = queryParameters?.encode(), !queryString.isEmpty { + components.percentEncodedQuery = queryString } if let bodyParameters = bodyParameters { @@ -145,3 +142,11 @@ public extension Request { return try response(from: passedObject, urlResponse: urlResponse) } } + +public protocol JSONRequest: Request {} + +public extension JSONRequest { + var dataParser: JSONDataParser { + return JSONDataParser(readingOptions: []) + } +} diff --git a/Sources/APIKit/Session.swift b/Sources/APIKit/Session.swift index 4406fca9..0bd73d56 100644 --- a/Sources/APIKit/Session.swift +++ b/Sources/APIKit/Session.swift @@ -36,8 +36,8 @@ open class Session { /// - parameter handler: The closure that receives result of the request. /// - returns: The new session task. @discardableResult - open class func send(_ request: Request, callbackQueue: CallbackQueue? = nil, handler: @escaping (Result) -> Void = { _ in }) -> SessionTask? { - return shared.send(request, callbackQueue: callbackQueue, handler: handler) + open class func send(_ request: Request, callbackQueue: CallbackQueue? = nil, progressHandler: @escaping (Int64, Int64, Int64) -> Void = { _,_,_ in }, completionHandler: @escaping (Result) -> Void = { _ in }) -> SessionTask? { + return shared.send(request, callbackQueue: callbackQueue, progressHandler: progressHandler, completionHandler: completionHandler) } /// Calls `cancelRequests(with:passingTest:)` of `Session.shared`. @@ -54,7 +54,7 @@ open class Session { /// - parameter handler: The closure that receives result of the request. /// - returns: The new session task. @discardableResult - open func send(_ request: Request, callbackQueue: CallbackQueue? = nil, handler: @escaping (Result) -> Void = { _ in }) -> SessionTask? { + open func send(_ request: Request, callbackQueue: CallbackQueue? = nil, progressHandler: @escaping (Int64, Int64, Int64) -> Void = { _,_,_ in }, completionHandler: @escaping (Result) -> Void = { _ in }) -> SessionTask? { let callbackQueue = callbackQueue ?? self.callbackQueue let urlRequest: URLRequest @@ -62,33 +62,38 @@ open class Session { urlRequest = try request.buildURLRequest() } catch { callbackQueue.execute { - handler(.failure(.requestError(error))) + completionHandler(.failure(.requestError(error))) } return nil } - let task = adapter.createTask(with: urlRequest) { data, urlResponse, error in - let result: Result - - switch (data, urlResponse, error) { - case (_, _, let error?): - result = .failure(.connectionError(error)) + let task = adapter.createTask(with: urlRequest, + progressHandler: { bytesSent, totalBytesSent, totalBytesExpectedToSend in + progressHandler(bytesSent, totalBytesSent, totalBytesExpectedToSend) + }, + completionHandler: { data, urlResponse, error in + let result: Result + + switch (data, urlResponse, error) { + case (_, _, let error?): + result = .failure(.connectionError(error)) + + case (let data?, let urlResponse as HTTPURLResponse, _): + do { + result = .success(try request.parse(data: data as Data, urlResponse: urlResponse)) + } catch { + result = .failure(.responseError(error)) + } - case (let data?, let urlResponse as HTTPURLResponse, _): - do { - result = .success(try request.parse(data: data as Data, urlResponse: urlResponse)) - } catch { - result = .failure(.responseError(error)) + default: + result = .failure(.responseError(ResponseError.nonHTTPURLResponse(urlResponse))) } - default: - result = .failure(.responseError(ResponseError.nonHTTPURLResponse(urlResponse))) - } - - callbackQueue.execute { - handler(result) + callbackQueue.execute { + completionHandler(result) + } } - } + ) setRequest(request, forTask: task) task.resume() diff --git a/Sources/APIKit/SessionAdapter/SessionAdapter.swift b/Sources/APIKit/SessionAdapter/SessionAdapter.swift index d123dae0..3db2b583 100644 --- a/Sources/APIKit/SessionAdapter/SessionAdapter.swift +++ b/Sources/APIKit/SessionAdapter/SessionAdapter.swift @@ -11,7 +11,7 @@ public protocol SessionTask: class { /// with `Session`. public protocol SessionAdapter { /// Returns instance that conforms to `SessionTask`. `handler` must be called after success or failure. - func createTask(with URLRequest: URLRequest, handler: @escaping (Data?, URLResponse?, Error?) -> Void) -> SessionTask + func createTask(with URLRequest: URLRequest, progressHandler: @escaping (Int64, Int64, Int64) -> Void, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> SessionTask /// Collects tasks from backend networking stack. `handler` must be called after collecting. func getTasks(with handler: @escaping ([SessionTask]) -> Void) diff --git a/Sources/APIKit/SessionAdapter/URLSessionAdapter.swift b/Sources/APIKit/SessionAdapter/URLSessionAdapter.swift index 38ac15ef..3077dc8c 100644 --- a/Sources/APIKit/SessionAdapter/URLSessionAdapter.swift +++ b/Sources/APIKit/SessionAdapter/URLSessionAdapter.swift @@ -6,6 +6,7 @@ extension URLSessionTask: SessionTask { private var dataTaskResponseBufferKey = 0 private var taskAssociatedObjectCompletionHandlerKey = 0 +private var taskAssociatedObjectProgressHandlerKey = 0 /// `URLSessionAdapter` connects `URLSession` with `Session`. /// @@ -25,11 +26,12 @@ open class URLSessionAdapter: NSObject, SessionAdapter, URLSessionDelegate, URLS } /// Creates `URLSessionDataTask` instance using `dataTaskWithRequest(_:completionHandler:)`. - open func createTask(with URLRequest: URLRequest, handler: @escaping (Data?, URLResponse?, Error?) -> Void) -> SessionTask { + open func createTask(with URLRequest: URLRequest, progressHandler: @escaping (Int64, Int64, Int64) -> Void, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> SessionTask { let task = urlSession.dataTask(with: URLRequest) setBuffer(NSMutableData(), forTask: task) - setHandler(handler, forTask: task) + setHandler(completionHandler, forTask: task) + setProgressHandler(progressHandler, forTask: task) return task } @@ -58,6 +60,13 @@ open class URLSessionAdapter: NSObject, SessionAdapter, URLSessionDelegate, URLS return objc_getAssociatedObject(task, &taskAssociatedObjectCompletionHandlerKey) as? (Data?, URLResponse?, Error?) -> Void } + private func setProgressHandler(_ progressHandler: @escaping (Int64, Int64, Int64) -> Void, forTask task: URLSessionTask) { + objc_setAssociatedObject(task, &taskAssociatedObjectProgressHandlerKey, progressHandler as Any, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + + private func progressHandler(for task: URLSessionTask) -> ((Int64, Int64, Int64) -> Void)? { + return objc_getAssociatedObject(task, &taskAssociatedObjectProgressHandlerKey) as? (Int64, Int64, Int64) -> Void + } // MARK: URLSessionTaskDelegate open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { handler(for: task)?(buffer(for: task) as Data?, task.response, error) @@ -67,4 +76,9 @@ open class URLSessionAdapter: NSObject, SessionAdapter, URLSessionDelegate, URLS open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { buffer(for: dataTask)?.append(data) } + + // MARK: URLSessionDataDelegate + open func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + progressHandler(for: task)?(bytesSent, totalBytesSent, totalBytesExpectedToSend) + } } diff --git a/Tests/APIKitTests/QueryParameters/URLEncodedQueryParametersTests.swift b/Tests/APIKitTests/QueryParameters/URLEncodedQueryParametersTests.swift new file mode 100644 index 00000000..a764e00c --- /dev/null +++ b/Tests/APIKitTests/QueryParameters/URLEncodedQueryParametersTests.swift @@ -0,0 +1,19 @@ +import XCTest +import APIKit + +class URLEncodedQueryParametersTests: XCTestCase { + func testURLEncodedSuccess() { + let object: [String: Any] = ["foo": "string", "bar": 1, "q": "こんにちは"] + let parameters = URLEncodedQueryParameters(parameters: object) + guard let query = parameters.encode() else { + XCTFail() + return + } + + let items = query.components(separatedBy: "&") + XCTAssertEqual(items.count, 3) + XCTAssertTrue(items.contains("foo=string")) + XCTAssertTrue(items.contains("bar=1")) + XCTAssertTrue(items.contains("q=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF")) + } +} diff --git a/Tests/APIKitTests/SessionAdapterType/URLSessionAdapterSubclassTests.swift b/Tests/APIKitTests/SessionAdapterType/URLSessionAdapterSubclassTests.swift index 4820dde3..a75b329e 100644 --- a/Tests/APIKitTests/SessionAdapterType/URLSessionAdapterSubclassTests.swift +++ b/Tests/APIKitTests/SessionAdapterType/URLSessionAdapterSubclassTests.swift @@ -15,6 +15,11 @@ class URLSessionAdapterSubclassTests: XCTestCase { functionCallFlags[(#function)] = true super.urlSession(session, dataTask: dataTask, didReceive: data) } + + override func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + functionCallFlags[(#function)] = true + super.urlSession(session, task: task, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend) + } } var adapter: SessionAdapter! @@ -50,4 +55,20 @@ class URLSessionAdapterSubclassTests: XCTestCase { XCTAssertEqual(adapter.functionCallFlags["urlSession(_:task:didCompleteWithError:)"], true) XCTAssertEqual(adapter.functionCallFlags["urlSession(_:dataTask:didReceive:)"], true) } + + // Limitation: 'urlSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:' delegate method will never be called when you stub the request using subclass of URLProtocol. + func testDelegateProgressMethodCall() { + let expectation = self.expectation(description: "wait for response") + let request = TestRequest(baseURL: "https://httpbin.org", path: "/post", method: .post) + let configuration = URLSessionConfiguration.default + let adapter = SessionAdapter(configuration: configuration) + let session = Session(adapter: adapter) + + session.send(request, progressHandler: { _, _, _ in + expectation.fulfill() + }) + + waitForExpectations(timeout: 10.0, handler: nil) + XCTAssertEqual(adapter.functionCallFlags["urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)"], true) + } } diff --git a/Tests/APIKitTests/SessionTests.swift b/Tests/APIKitTests/SessionTests.swift index d65a235e..f4908a17 100644 --- a/Tests/APIKitTests/SessionTests.swift +++ b/Tests/APIKitTests/SessionTests.swift @@ -174,7 +174,7 @@ class SessionTests: XCTestCase { waitForExpectations(timeout: 1.0, handler: nil) } - struct AnotherTestRequest: Request { + struct AnotherTestRequest: JSONRequest { typealias Response = Void var baseURL: URL { @@ -222,6 +222,23 @@ class SessionTests: XCTestCase { waitForExpectations(timeout: 1.0, handler: nil) } + func testProgress() { + let dictionary = ["key": "value"] + adapter.data = try! JSONSerialization.data(withJSONObject: dictionary, options: []) + + let expectation = self.expectation(description: "wait for response") + let request = TestRequest(method: .post) + + session.send(request, progressHandler: { bytesSent, totalBytesSent, totalBytesExpectedToSend in + XCTAssertNotNil(bytesSent) + XCTAssertNotNil(totalBytesSent) + XCTAssertNotNil(totalBytesExpectedToSend) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1.0, handler: nil) + } + // MARK: Class methods func testSharedSession() { XCTAssert(Session.shared === Session.shared) @@ -237,12 +254,13 @@ class SessionTests: XCTestCase { return testSesssion } - override func send(_ request: Request, callbackQueue: CallbackQueue?, handler: @escaping (Result) -> Void) -> SessionTask? { + override func send(_ request: Request, callbackQueue: CallbackQueue?, progressHandler: @escaping (Int64, Int64, Int64) -> Void, completionHandler: @escaping (Result) -> Void) -> SessionTask? { + functionCallFlags[(#function)] = true return super.send(request) } - override func cancelRequests(with requestType: Request.Type, passingTest test: @escaping (Request) -> Bool) { + override func cancelRequests(with requestType: Request.Type, passingTest test: @escaping (Request) -> Bool) { functionCallFlags[(#function)] = true } } @@ -251,7 +269,7 @@ class SessionTests: XCTestCase { SessionSubclass.send(TestRequest()) SessionSubclass.cancelRequests(with: TestRequest.self) - XCTAssertEqual(testSession.functionCallFlags["send(_:callbackQueue:handler:)"], true) + XCTAssertEqual(testSession.functionCallFlags["send(_:callbackQueue:progressHandler:completionHandler:)"], true) XCTAssertEqual(testSession.functionCallFlags["cancelRequests(with:passingTest:)"], true) } } diff --git a/Tests/APIKitTests/TestComponents/TestRequest.swift b/Tests/APIKitTests/TestComponents/TestRequest.swift index e3bc69aa..32cd5317 100644 --- a/Tests/APIKitTests/TestComponents/TestRequest.swift +++ b/Tests/APIKitTests/TestComponents/TestRequest.swift @@ -1,7 +1,7 @@ import Foundation import APIKit -struct TestRequest: Request { +struct TestRequest: JSONRequest { var absoluteURL: URL? { let urlRequest = try? buildURLRequest() return urlRequest?.url diff --git a/Tests/APIKitTests/TestComponents/TestSessionAdapter.swift b/Tests/APIKitTests/TestComponents/TestSessionAdapter.swift index 4813c23c..b9c3cdb1 100644 --- a/Tests/APIKitTests/TestComponents/TestSessionAdapter.swift +++ b/Tests/APIKitTests/TestComponents/TestSessionAdapter.swift @@ -40,17 +40,18 @@ class TestSessionAdapter: SessionAdapter { func executeAllTasks() { for task in tasks { if task.cancelled { - task.handler(nil, nil, Error.cancelled) + task.completionHandler(nil, nil, Error.cancelled) } else { - task.handler(data, urlResponse, error) + task.progressHandler(1, 1, 1) + task.completionHandler(data, urlResponse, error) } } tasks = [] } - func createTask(with URLRequest: URLRequest, handler: @escaping (Data?, URLResponse?, Swift.Error?) -> Void) -> SessionTask { - let task = TestSessionTask(handler: handler) + func createTask(with URLRequest: URLRequest, progressHandler: @escaping (Int64, Int64, Int64) -> Void, completionHandler: @escaping (Data?, URLResponse?, Swift.Error?) -> Void) -> SessionTask { + let task = TestSessionTask(progressHandler: progressHandler, completionHandler: completionHandler) tasks.append(task) return task diff --git a/Tests/APIKitTests/TestComponents/TestSessionTask.swift b/Tests/APIKitTests/TestComponents/TestSessionTask.swift index 5bf7927a..af230b48 100644 --- a/Tests/APIKitTests/TestComponents/TestSessionTask.swift +++ b/Tests/APIKitTests/TestComponents/TestSessionTask.swift @@ -2,12 +2,14 @@ import Foundation import APIKit class TestSessionTask: SessionTask { - - var handler: (Data?, URLResponse?, Error?) -> Void + + var completionHandler: (Data?, URLResponse?, Error?) -> Void + var progressHandler: (Int64, Int64, Int64) -> Void var cancelled = false - init(handler: @escaping (Data?, URLResponse?, Error?) -> Void) { - self.handler = handler + init(progressHandler: @escaping (Int64, Int64, Int64) -> Void, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) { + self.completionHandler = completionHandler + self.progressHandler = progressHandler } func resume() {