From 04e825693680a5cbbeebfe1e55a7275e072c2126 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 25 Sep 2024 07:32:58 -1000 Subject: [PATCH 01/25] WIP - Migrates to Swift 6 --- Package.swift | 2 +- .../Calls/NetworkingClient+Data.swift | 26 +- .../Calls/NetworkingClient+Decodable.swift | 24 +- .../Calls/NetworkingClient+JSON.swift | 104 ++-- .../Calls/NetworkingClient+Multipart.swift | 39 +- .../Calls/NetworkingClient+Requests.swift | 66 +-- .../Calls/NetworkingClient+Void.swift | 12 +- Sources/Networking/NetworkingClient.swift | 41 +- Sources/Networking/NetworkingError.swift | 4 +- Sources/Networking/NetworkingRequest.swift | 231 +++++---- Sources/Networking/NetworkingService.swift | 475 +++++++++--------- Sources/Networking/Params.swift | 2 +- 12 files changed, 549 insertions(+), 477 deletions(-) diff --git a/Package.swift b/Package.swift index 4cbb066..05eaa1c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Sources/Networking/Calls/NetworkingClient+Data.swift b/Sources/Networking/Calls/NetworkingClient+Data.swift index e912393..356a745 100644 --- a/Sources/Networking/Calls/NetworkingClient+Data.swift +++ b/Sources/Networking/Calls/NetworkingClient+Data.swift @@ -11,57 +11,57 @@ import Combine public extension NetworkingClient { func get(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.get, route, params: params).publisher() + publisher(request: request(.get, route, params: params)) } func post(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.post, route, params: params).publisher() + publisher(request: request(.post, route, params: params)) } func post(_ route: String, body: Encodable) -> AnyPublisher { - request(.post, route, encodableBody: body).publisher() + publisher(request: request(.post, route, encodableBody: body)) } func put(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.put, route, params: params).publisher() + publisher(request: request(.put, route, params: params)) } func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.patch, route, params: params).publisher() + publisher(request: request(.patch, route, params: params)) } func patch(_ route: String, body: Encodable) -> AnyPublisher { - request(.patch, route, encodableBody: body).publisher() + publisher(request: request(.patch, route, encodableBody: body)) } func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.delete, route, params: params).publisher() + publisher(request: request(.delete, route, params: params)) } } public extension NetworkingClient { func get(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.get, route, params: params).execute() + try await execute(request: request(.get, route, params: params)) } func post(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.post, route, params: params).execute() + try await execute(request: request(.post, route, params: params)) } func post(_ route: String, body: Encodable) async throws -> Data { - try await request(.post, route, encodableBody: body).execute() + try await execute(request: request(.post, route, encodableBody: body)) } func put(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.put, route, params: params).execute() + try await execute(request: request(.put, route, params: params)) } func patch(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.patch, route, params: params).execute() + try await execute(request: request(.patch, route, params: params)) } func delete(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.delete, route, params: params).execute() + try await execute(request: request(.delete, route, params: params)) } } diff --git a/Sources/Networking/Calls/NetworkingClient+Decodable.swift b/Sources/Networking/Calls/NetworkingClient+Decodable.swift index e48ff5b..fc53093 100644 --- a/Sources/Networking/Calls/NetworkingClient+Decodable.swift +++ b/Sources/Networking/Calls/NetworkingClient+Decodable.swift @@ -138,7 +138,7 @@ public extension NetworkingClient { func get(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { - let json: Any = try await get(route, params: params) + let json: JSON = try await get(route, params: params) let model:T = try self.toModel(json, keypath: keypath) return model } @@ -147,14 +147,14 @@ public extension NetworkingClient { params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await get(route, params: params) + let json: JSON = try await get(route, params: params) return try self.toModel(json, keypath: keypath) } func post(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { - let json: Any = try await post(route, params: params) + let json: JSON = try await post(route, params: params) return try self.toModel(json, keypath: keypath) } @@ -162,7 +162,7 @@ public extension NetworkingClient { body: Encodable, keypath: String? = nil ) async throws -> T { - let json: Any = try await post(route, body: body) + let json: JSON = try await post(route, body: body) return try self.toModel(json, keypath: keypath) } @@ -170,14 +170,14 @@ public extension NetworkingClient { params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await post(route, params: params) + let json: JSON = try await post(route, params: params) return try self.toModel(json, keypath: keypath) } func put(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { - let json: Any = try await put(route, params: params) + let json: JSON = try await put(route, params: params) return try self.toModel(json, keypath: keypath) } @@ -185,14 +185,14 @@ public extension NetworkingClient { params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await put(route, params: params) + let json: JSON = try await put(route, params: params) return try self.toModel(json, keypath: keypath) } func patch(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { - let json: Any = try await patch(route, params: params) + let json: JSON = try await patch(route, params: params) return try self.toModel(json, keypath: keypath) } @@ -200,7 +200,7 @@ public extension NetworkingClient { params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await patch(route, params: params) + let json: JSON = try await patch(route, params: params) return try self.toModel(json, keypath: keypath) } @@ -208,14 +208,14 @@ public extension NetworkingClient { body: Encodable, keypath: String? = nil ) async throws -> T { - let json: Any = try await patch(route, body: body) + let json: JSON = try await patch(route, body: body) return try self.toModel(json, keypath: keypath) } func delete(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { - let json: Any = try await delete(route, params: params) + let json: JSON = try await delete(route, params: params) return try self.toModel(json, keypath: keypath) } @@ -223,7 +223,7 @@ public extension NetworkingClient { params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await delete(route, params: params) + let json: JSON = try await delete(route, params: params) return try self.toModel(json, keypath: keypath) } } diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift index 677b6db..f7e57ec 100644 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ b/Sources/Networking/Calls/NetworkingClient+JSON.swift @@ -10,86 +10,128 @@ import Combine public extension NetworkingClient { - func get(_ route: String, params: Params = Params()) -> AnyPublisher { + func get(_ route: String, params: Params = Params()) -> AnyPublisher { get(route, params: params).toJSON() } - func post(_ route: String, params: Params = Params()) -> AnyPublisher { + func post(_ route: String, params: Params = Params()) -> AnyPublisher { post(route, params: params).toJSON() } - func post(_ route: String, body: Encodable) -> AnyPublisher { + func post(_ route: String, body: Encodable) -> AnyPublisher { post(route, body: body).toJSON() } - func put(_ route: String, params: Params = Params()) -> AnyPublisher { + func put(_ route: String, params: Params = Params()) -> AnyPublisher { put(route, params: params).toJSON() } - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { + func patch(_ route: String, params: Params = Params()) -> AnyPublisher { patch(route, params: params).toJSON() } - func patch(_ route: String, body: Encodable) -> AnyPublisher { + func patch(_ route: String, body: Encodable) -> AnyPublisher { patch(route, body: body).toJSON() } - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { delete(route, params: params).toJSON() } } public extension NetworkingClient { - func get(_ route: String, params: Params = Params()) async throws -> Any { + func get(_ route: String, params: Params = Params()) async throws -> JSON { let req = request(.get, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) } - func post(_ route: String, params: Params = Params()) async throws -> Any { + func post(_ route: String, params: Params = Params()) async throws -> JSON { let req = request(.post, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) } - func post(_ route: String, body: Encodable) async throws -> Any { + func post(_ route: String, body: Encodable) async throws -> JSON { let req = request(.post, route, encodableBody: body) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) } - func put(_ route: String, params: Params = Params()) async throws -> Any { + func put(_ route: String, params: Params = Params()) async throws -> JSON { let req = request(.put, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) } - func patch(_ route: String, params: Params = Params()) async throws -> Any { + func patch(_ route: String, params: Params = Params()) async throws -> JSON { let req = request(.patch, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) } - func patch(_ route: String, body: Encodable) async throws -> Any { + func patch(_ route: String, body: Encodable) async throws -> JSON { let req = request(.patch, route, encodableBody: body) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) } - func delete(_ route: String, params: Params = Params()) async throws -> Any { + func delete(_ route: String, params: Params = Params()) async throws -> JSON { let req = request(.delete, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) } } // Data to JSON extension Publisher where Output == Data { - public func toJSON() -> AnyPublisher { - tryMap { data -> Any in - return try JSONSerialization.jsonObject(with: data, options: []) + public func toJSON() -> AnyPublisher { + tryMap { data -> JSON in + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) }.eraseToAnyPublisher() } } + + +public struct JSON: Sendable, CustomStringConvertible { + + let array: [any Sendable]? + let dictionary: [String: any Sendable]? + + init(jsonObject: Any) { + if let arr = jsonObject as? [Sendable] { + array = arr + dictionary = nil + } else if let dic = jsonObject as? [String: any Sendable] { + dictionary = dic + array = nil + } else { + array = nil + dictionary = nil + } + } + + var value: Any { + return array ?? dictionary ?? "" + } + + public var description: String { + if let array = array { + return String(describing: array) + } else if let dictionary = dictionary { + return String(describing: dictionary) + } + return "empty" + } + +} diff --git a/Sources/Networking/Calls/NetworkingClient+Multipart.swift b/Sources/Networking/Calls/NetworkingClient+Multipart.swift index 3207083..59f6e47 100644 --- a/Sources/Networking/Calls/NetworkingClient+Multipart.swift +++ b/Sources/Networking/Calls/NetworkingClient+Multipart.swift @@ -32,24 +32,45 @@ public extension NetworkingClient { func post(_ route: String, params: Params = Params(), multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { - let req = request(.post, route, params: params) - req.multipartData = multipartData - return req.uploadPublisher() + let req = NetworkingRequest( + method: .post, + url: baseURL + route, + parameterEncoding: parameterEncoding, + params: params, + encodableBody: nil, + headers: headers, + multipartData: multipartData, + timeout: timeout) + return uploadPublisher(request: req) } func put(_ route: String, params: Params = Params(), multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { - let req = request(.put, route, params: params) - req.multipartData = multipartData - return req.uploadPublisher() + let req = NetworkingRequest( + method: .put, + url: baseURL + route, + parameterEncoding: parameterEncoding, + params: params, + encodableBody: nil, + headers: headers, + multipartData: multipartData, + timeout: timeout) + return uploadPublisher(request: req) } func patch(_ route: String, params: Params = Params(), multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { - let req = request(.patch, route, params: params) - req.multipartData = multipartData - return req.uploadPublisher() + let req = NetworkingRequest( + method: .patch, + url: baseURL + route, + parameterEncoding: parameterEncoding, + params: params, + encodableBody: nil, + headers: headers, + multipartData: multipartData, + timeout: timeout) + return uploadPublisher(request: req) } } diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index e891f5d..e09e1e4 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -34,28 +34,15 @@ public extension NetworkingClient { _ route: String, params: Params = Params() ) -> NetworkingRequest { - let req = NetworkingRequest() - req.httpMethod = httpMethod - req.route = route - req.params = params - - let updateRequest = { [weak req, weak self] in - guard let self = self else { return } - req?.baseURL = self.baseURL - req?.logLevel = self.logLevel - req?.headers = self.headers - req?.parameterEncoding = self.parameterEncoding - req?.sessionConfiguration = self.sessionConfiguration - req?.timeout = self.timeout - } - updateRequest() - req.requestRetrier = { [weak self] in - self?.requestRetrier?($0, $1)? - .handleEvents(receiveOutput: { _ in - updateRequest() - }) - .eraseToAnyPublisher() - } + let req = NetworkingRequest( + method: httpMethod, + url: baseURL + route, + parameterEncoding: parameterEncoding, + params: params, + encodableBody: nil, + headers: headers, + multipartData: nil, + timeout: timeout) return req } @@ -64,29 +51,18 @@ public extension NetworkingClient { params: Params = Params(), encodableBody: Encodable? = nil ) -> NetworkingRequest { - let req = NetworkingRequest() - req.httpMethod = httpMethod - req.route = route - req.params = Params() - req.encodableBody = encodableBody - - let updateRequest = { [weak req, weak self] in - guard let self = self else { return } - req?.baseURL = self.baseURL - req?.logLevel = self.logLevel - req?.headers = self.headers - req?.parameterEncoding = self.parameterEncoding - req?.sessionConfiguration = self.sessionConfiguration - req?.timeout = self.timeout - } - updateRequest() - req.requestRetrier = { [weak self] in - self?.requestRetrier?($0, $1)? - .handleEvents(receiveOutput: { _ in - updateRequest() - }) - .eraseToAnyPublisher() - } + let req = NetworkingRequest( + method: httpMethod, + url: baseURL + route, + parameterEncoding: parameterEncoding, + params: params, + encodableBody: encodableBody, + headers: headers, + multipartData: nil, + timeout: timeout) return req } } + + +// TODO handle retries diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift index dea4a75..6a9f32b 100644 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ b/Sources/Networking/Calls/NetworkingClient+Void.swift @@ -57,31 +57,31 @@ public extension NetworkingClient { func get(_ route: String, params: Params = Params()) async throws { let req = request(.get, route, params: params) - _ = try await req.execute() + _ = try await execute(request: req) } func post(_ route: String, params: Params = Params()) async throws { let req = request(.post, route, params: params) - _ = try await req.execute() + _ = try await execute(request: req) } func post(_ route: String, body: Encodable) async throws { let req = request(.post, route, encodableBody: body) - _ = try await req.execute() + _ = try await execute(request: req) } func put(_ route: String, params: Params = Params()) async throws { let req = request(.put, route, params: params) - _ = try await req.execute() + _ = try await execute(request: req) } func patch(_ route: String, params: Params = Params()) async throws { let req = request(.patch, route, params: params) - _ = try await req.execute() + _ = try await execute(request: req) } func delete(_ route: String, params: Params = Params()) async throws { let req = request(.delete, route, params: params) - _ = try await req.execute() + _ = try await execute(request: req) } } diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index b3c9dfb..41e6c10 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -1,7 +1,24 @@ import Foundation import Combine -public class NetworkingClient { +actor NetworkingClientURLSessionDelegate: NSObject, URLSessionDelegate { + + let progressPublisher = PassthroughSubject() + + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) { + let progress = Progress(totalUnitCount: totalBytesExpectedToSend) + progress.completedUnitCount = totalBytesSent + progressPublisher.send(progress) + } +} + +public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? + +public actor NetworkingClient { /** Instead of using the same keypath for every call eg: "collection", this enables to use a default keypath for parsing collections. @@ -17,6 +34,7 @@ public class NetworkingClient { public var requestRetrier: NetworkRequestRetrier? public var jsonDecoderFactory: (() -> JSONDecoder)? + let sessionDelegate = NetworkingClientURLSessionDelegate() /** Prints network calls to the console. Values Available are .None, Calls and CallsAndResponses. @@ -27,14 +45,14 @@ public class NetworkingClient { set { logger.logLevel = newValue } } - private let logger = NetworkingLogger() + internal let logger = NetworkingLogger() public init(baseURL: String, timeout: TimeInterval? = nil) { self.baseURL = baseURL self.timeout = timeout } - public func toModel(_ json: Any, keypath: String? = nil) throws -> T { + public func toModel(_ json: JSON, keypath: String? = nil) throws -> T { do { let data = resourceData(from: json, keypath: keypath) return try T.decode(data) @@ -43,7 +61,7 @@ public class NetworkingClient { } } - public func toModel(_ json: Any, keypath: String? = nil) throws -> T { + public func toModel(_ json: JSON, keypath: String? = nil) throws -> T { do { let jsonObject = resourceData(from: json, keypath: keypath) let decoder = jsonDecoderFactory?() ?? JSONDecoder() @@ -55,7 +73,7 @@ public class NetworkingClient { } } - public func toModels(_ json: Any, keypath: String? = nil) throws -> [T] { + public func toModels(_ json: JSON, keypath: String? = nil) throws -> [T] { do { guard let array = resourceData(from: json, keypath: keypath) as? [Any] else { return [T]() @@ -68,7 +86,7 @@ public class NetworkingClient { } } - public func toModels(_ json: Any, keypath: String? = nil) throws -> [T] { + public func toModels(_ json: JSON, keypath: String? = nil) throws -> [T] { do { guard let array = resourceData(from: json, keypath: keypath) as? [Any] else { return [T]() @@ -84,11 +102,14 @@ public class NetworkingClient { } } - private func resourceData(from json: Any, keypath: String?) -> Any { - if let keypath = keypath, !keypath.isEmpty, let dic = json as? [String: Any], let val = dic[keypath] { - return val is NSNull ? json : val + private func resourceData(from json: JSON, keypath: String?) -> Any { + if let keypath = keypath, !keypath.isEmpty, let dic = json.dictionary, let val = dic[keypath] { + return val is NSNull ? json.value : val } - return json + return json.value } } +extension NetworkingClient { + +} diff --git a/Sources/Networking/NetworkingError.swift b/Sources/Networking/NetworkingError.swift index 5d69025..39e577e 100644 --- a/Sources/Networking/NetworkingError.swift +++ b/Sources/Networking/NetworkingError.swift @@ -9,7 +9,7 @@ import Foundation public struct NetworkingError: Error, LocalizedError { - public enum Status: Int { + public enum Status: Int, Sendable { case unknown = -1 case networkUnreachable = 0 @@ -144,7 +144,7 @@ public struct NetworkingError: Error, LocalizedError { public var status: Status public var code: Int { return status.rawValue } - public var jsonPayload: Any? + public var jsonPayload: JSON? public init(errorCode: Int) { self.status = Status(rawValue: errorCode) ?? .unknown diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index 8b5794d..22c5479 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -6,41 +6,39 @@ // import Foundation -import Combine +@preconcurrency import Combine + + -public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? -public class NetworkingRequest: NSObject, URLSessionTaskDelegate { - - var parameterEncoding = ParameterEncoding.urlEncoded - var baseURL = "" - var route = "" - var httpMethod = HTTPMethod.get - public var params = Params() - public var encodableBody: Encodable? - var headers = [String: String]() - var multipartData: [MultipartData]? - var logLevel: NetworkingLogLevel { - get { return logger.logLevel } - set { logger.logLevel = newValue } - } - private let logger = NetworkingLogger() - var timeout: TimeInterval? - let progressPublisher = PassthroughSubject() - var sessionConfiguration: URLSessionConfiguration? - var requestRetrier: NetworkRequestRetrier? - private let maxRetryCount = 3 - public func uploadPublisher() -> AnyPublisher<(Data?, Progress), Error> { +public struct NetworkingRequest { + let method: HTTPMethod + let url: String + let parameterEncoding: ParameterEncoding + let params: Params + let encodableBody: Encodable? + let headers: [String: String] + let multipartData: [MultipartData]? + let timeout: TimeInterval? + let maxRetryCount = 3 +// var logLevel: NetworkingLogLevel { +// get { return logger.logLevel } +// set { logger.logLevel = newValue } +// } +} + +extension NetworkingClient { + + public func uploadPublisher(request: NetworkingRequest) -> AnyPublisher<(Data?, Progress), Error> { - guard let urlRequest = buildURLRequest() else { + guard let urlRequest = request.buildURLRequest() else { return Fail(error: NetworkingError.unableToParseRequest as Error) .eraseToAnyPublisher() } logger.log(request: urlRequest) - let config = sessionConfiguration ?? URLSessionConfiguration.default - let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil) + let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) let callPublisher: AnyPublisher<(Data?, Progress), Error> = urlSession.dataTaskPublisher(for: urlRequest) .tryMap { (data: Data, response: URLResponse) -> Data in self.logger.log(response: response, data: data) @@ -48,7 +46,7 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { if !(200...299 ~= httpURLResponse.statusCode) { var error = NetworkingError(errorCode: httpURLResponse.statusCode) if let json = try? JSONSerialization.jsonObject(with: data, options: []) { - error.jsonPayload = json + error.jsonPayload = JSON(jsonObject: json) } throw error } @@ -60,28 +58,32 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { return (data, Progress()) }.eraseToAnyPublisher() - let progressPublisher2: AnyPublisher<(Data?, Progress), Error> = progressPublisher - .map { progress -> (Data?, Progress) in - return (nil, progress) - }.eraseToAnyPublisher() - - return Publishers.Merge(callPublisher, progressPublisher2) - .receive(on: DispatchQueue.main).eraseToAnyPublisher() + return callPublisher + .eraseToAnyPublisher() + // Todo put back progress +// +// let progressPublisher2: AnyPublisher<(Data?, Progress), Error> = sessionDelegate.progressPublisher +// .map { progress -> (Data?, Progress) in +// return (nil, progress) +// }.eraseToAnyPublisher() +// +// return Publishers.Merge(callPublisher, progressPublisher2) +// .receive(on: DispatchQueue.main) +// .eraseToAnyPublisher() } - public func publisher() -> AnyPublisher { - publisher(retryCount: maxRetryCount) + public func publisher(request: NetworkingRequest) -> AnyPublisher { + publisher(request: request, retryCount: request.maxRetryCount) } - private func publisher(retryCount: Int) -> AnyPublisher { - guard let urlRequest = buildURLRequest() else { + private func publisher(request: NetworkingRequest, retryCount: Int) -> AnyPublisher { + guard let urlRequest = request.buildURLRequest() else { return Fail(error: NetworkingError.unableToParseRequest as Error) .eraseToAnyPublisher() } logger.log(request: urlRequest) - let config = sessionConfiguration ?? URLSessionConfiguration.default - let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil) + let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) return urlSession.dataTaskPublisher(for: urlRequest) .tryMap { (data: Data, response: URLResponse) -> Data in self.logger.log(response: response, data: data) @@ -89,76 +91,73 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { if !(200...299 ~= httpURLResponse.statusCode) { var error = NetworkingError(errorCode: httpURLResponse.statusCode) if let json = try? JSONSerialization.jsonObject(with: data, options: []) { - error.jsonPayload = json + error.jsonPayload = JSON(jsonObject: json) } throw error } } return data - }.tryCatch({ [weak self, urlRequest] error -> AnyPublisher in - guard - let self = self, - retryCount > 1, - let retryPublisher = self.requestRetrier?(urlRequest, error) - else { - throw error - } - return retryPublisher - .flatMap { _ -> AnyPublisher in - self.publisher(retryCount: retryCount - 1) - } - .eraseToAnyPublisher() - }).mapError { error -> NetworkingError in + } + // TODO fix retry +// .tryCatch({ [weak self, urlRequest] error -> AnyPublisher in +// guard +// let self = self, +// retryCount > 1, +// let retryPublisher = self.requestRetrier?(urlRequest, error) +// else { +// throw error +// } +// return retryPublisher +// .flatMap { _ -> AnyPublisher in +// self.publisher(request: request, retryCount: retryCount - 1) +// } +// .eraseToAnyPublisher() +// }) + .mapError { error -> NetworkingError in return NetworkingError(error: error) }.receive(on: DispatchQueue.main).eraseToAnyPublisher() } - func execute() async throws -> Data { - guard let urlRequest = buildURLRequest() else { + func execute(request: NetworkingRequest) async throws -> Data { + guard let urlRequest = request.buildURLRequest() else { throw NetworkingError.unableToParseRequest } logger.log(request: urlRequest) - let config = sessionConfiguration ?? URLSessionConfiguration.default - let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil) + let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) let (data, response) = try await urlSession.data(for: urlRequest) - self.logger.log(response: response, data: data) + logger.log(response: response, data: data) if let httpURLResponse = response as? HTTPURLResponse, !(200...299 ~= httpURLResponse.statusCode) { var error = NetworkingError(errorCode: httpURLResponse.statusCode) if let json = try? JSONSerialization.jsonObject(with: data, options: []) { - error.jsonPayload = json + error.jsonPayload = JSON(jsonObject: json) } throw error } return data - } - - private func getURLWithParams() -> String { - let urlString = baseURL + route - if params.isEmpty { return urlString } - guard let url = URL(string: urlString) else { - return urlString - } - if var urlComponents = URLComponents(url: url ,resolvingAgainstBaseURL: false) { - var queryItems = urlComponents.queryItems ?? [URLQueryItem]() - params.forEach { param in - // arrayParam[] syntax - if let array = param.value as? [CustomStringConvertible] { - array.forEach { - queryItems.append(URLQueryItem(name: "\(param.key)[]", value: "\($0)")) - } - } - queryItems.append(URLQueryItem(name: param.key, value: "\(param.value)")) - } - urlComponents.queryItems = queryItems - return urlComponents.url?.absoluteString ?? urlString - } - return urlString - } - +} + +// Thansks to https://stackoverflow.com/questions/26364914/http-request-in-swift-with-post-method +extension CharacterSet { + static let urlQueryValueAllowed: CharacterSet = { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + var allowed = CharacterSet.urlQueryAllowed + allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + return allowed + }() +} + +public enum ParameterEncoding { + case urlEncoded + case json +} + + +extension NetworkingRequest { internal func buildURLRequest() -> URLRequest? { - var urlString = baseURL + route - if httpMethod == .get { + var urlString = url + if method == .get { urlString = getURLWithParams() } @@ -167,7 +166,7 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { } var request = URLRequest(url: url) - if httpMethod != .get && multipartData == nil { + if method != .get && multipartData == nil { switch parameterEncoding { case .urlEncoded: request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") @@ -176,7 +175,7 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { } } - request.httpMethod = httpMethod.rawValue + request.httpMethod = method.rawValue for (key, value) in headers { request.setValue(value, forHTTPHeaderField: key) } @@ -185,7 +184,7 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { request.timeoutInterval = timeout } - if httpMethod != .get && multipartData == nil { + if method != .get && multipartData == nil { if let encodableBody { let jsonEncoder = JSONEncoder() do { @@ -215,6 +214,29 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { return request } + private func getURLWithParams() -> String { + let urlString = url + if params.isEmpty { return urlString } + guard let url = URL(string: urlString) else { + return urlString + } + if var urlComponents = URLComponents(url: url ,resolvingAgainstBaseURL: false) { + var queryItems = urlComponents.queryItems ?? [URLQueryItem]() + params.forEach { param in + // arrayParam[] syntax + if let array = param.value as? [CustomStringConvertible] { + array.forEach { + queryItems.append(URLQueryItem(name: "\(param.key)[]", value: "\($0)")) + } + } + queryItems.append(URLQueryItem(name: param.key, value: "\(param.value)")) + } + urlComponents.queryItems = queryItems + return urlComponents.url?.absoluteString ?? urlString + } + return urlString + } + private func buildMultipartHttpBody(params: Params, multiparts: [MultipartData], boundary: String) -> Data { // Combine all multiparts together let allMultiparts: [HttpBodyConvertible] = [params] + multiparts @@ -228,31 +250,4 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { .reduce(Data.init(), +) + boundaryEnding } - - public func urlSession(_ session: URLSession, - task: URLSessionTask, - didSendBodyData bytesSent: Int64, - totalBytesSent: Int64, - totalBytesExpectedToSend: Int64) { - let progress = Progress(totalUnitCount: totalBytesExpectedToSend) - progress.completedUnitCount = totalBytesSent - progressPublisher.send(progress) - } - -} - -// Thansks to https://stackoverflow.com/questions/26364914/http-request-in-swift-with-post-method -extension CharacterSet { - static let urlQueryValueAllowed: CharacterSet = { - let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 - let subDelimitersToEncode = "!$&'()*+,;=" - var allowed = CharacterSet.urlQueryAllowed - allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") - return allowed - }() -} - -public enum ParameterEncoding { - case urlEncoded - case json } diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index 096083e..ecbd4a2 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -18,215 +18,215 @@ public extension NetworkingService { // Data - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - network.patch(route, body: body) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // Void - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // JSON - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.delete(route, params: params, keypath: keypath) - } - - // Array Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.delete(route, params: params, keypath: keypath) - } - - // NetworkingJSONDecodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.delete(route, params: params, keypath: keypath) - } - - - - // Array NetworkingJSONDecodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.delete(route, params: params, keypath: keypath) - } +// func get(_ route: String, params: Params = Params()) async -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func patch(_ route: String, body: Encodable) -> AnyPublisher { +// network.patch(route, body: body) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // Void +// +// func get(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // JSON +// +// func get(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // Decodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.delete(route, params: params, keypath: keypath) +// } +// +// // Array Decodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.delete(route, params: params, keypath: keypath) +// } +// +// // NetworkingJSONDecodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.delete(route, params: params, keypath: keypath) +// } +// +// +// +// // Array NetworkingJSONDecodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.delete(route, params: params, keypath: keypath) +// } } // Async @@ -242,7 +242,7 @@ public extension NetworkingService { try await network.post(route, params: params) } - func post(_ route: String, body: Encodable) async throws -> Data { + func post(_ route: String, body: Encodable & Sendable) async throws -> Data { try await network.post(route, body: body) } @@ -268,7 +268,7 @@ public extension NetworkingService { return try await network.post(route, params: params) } - func post(_ route: String, body: Encodable) async throws { + func post(_ route: String, body: Encodable & Sendable) async throws { return try await network.post(route, body: body) } @@ -286,65 +286,82 @@ public extension NetworkingService { // JSON - func get(_ route: String, params: Params = Params()) async throws -> Any { + func get(_ route: String, params: Params = Params()) async throws -> JSON { try await network.get(route, params: params) } - func post(_ route: String, params: Params = Params()) async throws -> Any { + func post(_ route: String, params: Params = Params()) async throws -> JSON { try await network.post(route, params: params) } - func post(_ route: String, body: Encodable) async throws -> Any { + func post(_ route: String, body: Encodable & Sendable) async throws -> JSON { try await network.post(route, body: body) } - func put(_ route: String, params: Params = Params()) async throws -> Any { + func put(_ route: String, params: Params = Params()) async throws -> JSON { try await network.put(route, params: params) } - func patch(_ route: String, params: Params = Params()) async throws -> Any { + func patch(_ route: String, params: Params = Params()) async throws -> JSON { try await network.patch(route, params: params) } - func delete(_ route: String, params: Params = Params()) async throws -> Any { + func delete(_ route: String, params: Params = Params()) async throws -> JSON { try await network.delete(route, params: params) } // Decodable - func get(_ route: String, + func get(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { try await network.get(route, params: params, keypath: keypath) } + +// func get & Sendable>(_ route: String, +// params: Params = Params(), +// keypath: String? = nil, +// decodeVia: U.Type) async throws -> T { +// +// let mod :U = try await network.get(route, params: params, keypath: keypath) +// return mod.toModel() +// } + +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) async throws -> T where T.ENCODE: Sendable { +// let foo: T.ENCODE = try await get(route, params: params, keypath: keypath) +// return foo.toModel() +// } + - func post(_ route: String, + func post(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { try await network.post(route, params: params, keypath: keypath) } - func post(_ route: String, body: Encodable) async throws -> T { + func post(_ route: String, body: Encodable & Sendable) async throws -> T { try await network.post(route, body: body) } - func put(_ route: String, + func put(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { try await network.put(route, params: params, keypath: keypath) } - func patch(_ route: String, + func patch(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { try await network.patch(route, params: params, keypath: keypath) } - func patch(_ route: String, body: Encodable) async throws -> T { + func patch(_ route: String, body: Encodable & Sendable) async throws -> T { try await network.patch(route, body: body) } - func delete(_ route: String, + func delete(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T { try await network.delete(route, params: params, keypath: keypath) @@ -352,31 +369,31 @@ public extension NetworkingService { // Array Decodable - func get(_ route: String, + func get(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { try await network.get(route, params: params, keypath: keypath) } - func post(_ route: String, + func post(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { try await network.post(route, params: params, keypath: keypath) } - func put(_ route: String, + func put(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { try await network.put(route, params: params, keypath: keypath) } - func patch(_ route: String, + func patch(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { try await network.patch(route, params: params, keypath: keypath) } - func delete(_ route: String, + func delete(_ route: String, params: Params = Params(), keypath: String? = nil) async throws -> T where T: Collection { try await network.delete(route, params: params, keypath: keypath) diff --git a/Sources/Networking/Params.swift b/Sources/Networking/Params.swift index 129824e..ff0931e 100644 --- a/Sources/Networking/Params.swift +++ b/Sources/Networking/Params.swift @@ -7,7 +7,7 @@ import Foundation -public typealias Params = [String: CustomStringConvertible] +public typealias Params = [String: CustomStringConvertible & Sendable] extension Params { public func asPercentEncodedString(parentKey: String? = nil) -> String { From 38510c833309061e2efefd7fb340d44a3818f9a2 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 25 Sep 2024 07:39:20 -1000 Subject: [PATCH 02/25] Migrates CurlLoggingTests to Testing --- Tests/NetworkingTests/CurlLoggingTests.swift | 30 ++++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/Tests/NetworkingTests/CurlLoggingTests.swift b/Tests/NetworkingTests/CurlLoggingTests.swift index e733fa0..e695adc 100644 --- a/Tests/NetworkingTests/CurlLoggingTests.swift +++ b/Tests/NetworkingTests/CurlLoggingTests.swift @@ -5,20 +5,23 @@ // Created by Maxence Levelu on 25/01/2021. // +import Testing import Foundation -import XCTest -final class CurlLoggingTests: XCTestCase { +@Suite +struct CurlLoggingTests { - func testLogGet() { + @Test + func logGet() { var urlRequest = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com")!) urlRequest.httpMethod = "GET" urlRequest.addValue("token", forHTTPHeaderField: "Authorization") let result = urlRequest.toCurlCommand() - XCTAssertEqual(result, "curl \"https://jsonplaceholder.typicode.com\" \\\n\t-H 'Authorization: token'") + #expect(result == "curl \"https://jsonplaceholder.typicode.com\" \\\n\t-H 'Authorization: token'") } - func testLogPost() { + @Test + func logPost() { var urlRequest = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts")!) urlRequest.httpMethod = "POST" @@ -27,10 +30,11 @@ final class CurlLoggingTests: XCTestCase { """ urlRequest.httpBody = jsonString.data(using: .utf8) let result = urlRequest.toCurlCommand() - XCTAssertEqual(result, "curl \"https://jsonplaceholder.typicode.com/posts\" \\\n\t-X POST \\\n\t-d '{\"title\": \"Hello world\"}'") + #expect(result == "curl \"https://jsonplaceholder.typicode.com/posts\" \\\n\t-X POST \\\n\t-d '{\"title\": \"Hello world\"}'") } - func testLogPut() { + @Test + func logPut() { var urlRequest = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts")!) urlRequest.httpMethod = "PUT" @@ -39,10 +43,11 @@ final class CurlLoggingTests: XCTestCase { """ urlRequest.httpBody = jsonString.data(using: .utf8) let result = urlRequest.toCurlCommand() - XCTAssertEqual(result, "curl \"https://jsonplaceholder.typicode.com/posts\" \\\n\t-X PUT \\\n\t-d '{\"title\": \"Hello world\"}'") + #expect(result == "curl \"https://jsonplaceholder.typicode.com/posts\" \\\n\t-X PUT \\\n\t-d '{\"title\": \"Hello world\"}'") } - func testLogPatch() { + @Test + func logPatch() { var urlRequest = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts")!) urlRequest.httpMethod = "PATCH" @@ -51,14 +56,15 @@ final class CurlLoggingTests: XCTestCase { """ urlRequest.httpBody = jsonString.data(using: .utf8) let result = urlRequest.toCurlCommand() - XCTAssertEqual(result, "curl \"https://jsonplaceholder.typicode.com/posts\" \\\n\t-X PATCH \\\n\t-d '{\"title\": \"Hello world\"}'") + #expect(result == "curl \"https://jsonplaceholder.typicode.com/posts\" \\\n\t-X PATCH \\\n\t-d '{\"title\": \"Hello world\"}'") } - func testLogDelete() { + @Test + func logDelete() { var urlRequest = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts/1")!) urlRequest.httpMethod = "DELETE" let result = urlRequest.toCurlCommand() - XCTAssertEqual(result, "curl \"https://jsonplaceholder.typicode.com/posts/1\" \\\n\t-X DELETE") + #expect(result == "curl \"https://jsonplaceholder.typicode.com/posts/1\" \\\n\t-X DELETE") } } From c378bf0806bd48a8309828693e9e54835320c1b3 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 25 Sep 2024 07:39:42 -1000 Subject: [PATCH 03/25] Removes legacy linux test conf files --- Tests/LinuxMain.swift | 7 ------- Tests/NetworkingTests/XCTestManifests.swift | 9 --------- 2 files changed, 16 deletions(-) delete mode 100644 Tests/LinuxMain.swift delete mode 100644 Tests/NetworkingTests/XCTestManifests.swift diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 5ed3b04..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,7 +0,0 @@ -import XCTest - -import NetworkingTests - -var tests = [XCTestCaseEntry]() -tests += NetworkingTests.allTests() -XCTMain(tests) diff --git a/Tests/NetworkingTests/XCTestManifests.swift b/Tests/NetworkingTests/XCTestManifests.swift deleted file mode 100644 index dcc1e3e..0000000 --- a/Tests/NetworkingTests/XCTestManifests.swift +++ /dev/null @@ -1,9 +0,0 @@ -import XCTest - -#if !canImport(ObjectiveC) -public func allTests() -> [XCTestCaseEntry] { - return [ - testCase(NetworkingTests.allTests) - ] -} -#endif From e062cc4f0eb105e1eb0f19ba50c1450fef078f2d Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 25 Sep 2024 07:39:57 -1000 Subject: [PATCH 04/25] Migrates ParamsTests to Testing --- Tests/NetworkingTests/ParamsTests.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Tests/NetworkingTests/ParamsTests.swift b/Tests/NetworkingTests/ParamsTests.swift index d2bdcf9..340b458 100644 --- a/Tests/NetworkingTests/ParamsTests.swift +++ b/Tests/NetworkingTests/ParamsTests.swift @@ -1,16 +1,18 @@ -import XCTest +import Testing import Networking -final class ParamsTests: XCTestCase { +@Suite +struct ParamsTests { - func testAsPercentEncodedString() { + @Test + func asPercentEncodedString() { // Simple key value encoding - XCTAssertEqual("key=value", ["key": "value"].asPercentEncodedString()) + #expect("key=value" == ["key": "value"].asPercentEncodedString()) // Array-based key value encoding - XCTAssertEqual("key[]=value1&key[]=value2", ["key": ["value1", "value2"]].asPercentEncodedString()) + #expect("key[]=value1&key[]=value2" == ["key": ["value1", "value2"]].asPercentEncodedString()) // Dictionary-based key value encoding - XCTAssertEqual("key[subkey1]=value1", ["key": ["subkey1": "value1"]].asPercentEncodedString()) + #expect("key[subkey1]=value1" == ["key": ["subkey1": "value1"]].asPercentEncodedString()) } } From 620b856b908dade6b5c9be235039426f4505f01a Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 25 Sep 2024 07:53:24 -1000 Subject: [PATCH 05/25] WIP - Migrate unit tests to Testing --- .../NetworkingTests/DeleteRequestTests.swift | 626 +++++++-------- Tests/NetworkingTests/GetRequestTests.swift | 578 +++++++------- .../NetworkingTests/MockingURLProtocol.swift | 104 +-- .../MultipartRequestTests.swift | 222 +++--- Tests/NetworkingTests/NetworkingTests.swift | 62 +- Tests/NetworkingTests/PatchRequestTests.swift | 626 +++++++-------- Tests/NetworkingTests/PostRequestTests.swift | 732 +++++++++--------- Tests/NetworkingTests/PutRequestTests.swift | 624 +++++++-------- 8 files changed, 1789 insertions(+), 1785 deletions(-) diff --git a/Tests/NetworkingTests/DeleteRequestTests.swift b/Tests/NetworkingTests/DeleteRequestTests.swift index 64ae5cf..19ee5c0 100644 --- a/Tests/NetworkingTests/DeleteRequestTests.swift +++ b/Tests/NetworkingTests/DeleteRequestTests.swift @@ -1,316 +1,316 @@ +//// +//// DeleteRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// DeleteRequestTests.swift -// +//import Foundation +//import XCTest +//import Combine // -// Created by Sacha DSO on 12/04/2022. +//@testable +//import Networking // - -import Foundation -import XCTest -import Combine - -@testable -import Networking - -class DeletehRequestTests: XCTestCase { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - override func setUpWithError() throws { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - - func testDELETEVoidWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "Call works") - let expectationFinished = expectation(description: "Finished") - network.delete("/users").sink { completion in - switch completion { - case .failure(_): - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { () in - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testDELETEVoidAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _: Void = try await network.delete("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - } - - func testDELETEDataWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.delete("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (data: Data) in - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testDELETEDataAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = try await network.delete("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - func testDELETEJSONWorks() { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.delete("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (json: Any) in - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - - XCTAssertEqual(data, expectedResponseData) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testDELETEJSONAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: Any = try await network.delete("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - XCTAssertEqual(data, expectedResponseData) - } - - func testDELETENetworkingJSONDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.delete("/posts/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") - expectationFinished.fulfill() - } - } receiveValue: { (post: Post) in - XCTAssertEqual(post.title, "Hello") - XCTAssertEqual(post.content, "World") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testDELETEDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.delete("/users/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: UserJSON) in - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testDELETEDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON: UserJSON = try await network.delete("/users/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - } - - func testDELETEArrayOfDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.delete("/users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testDELETEArrayOfDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let users: [UserJSON] = try await network.delete("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") - } - - func testDELETEArrayOfDecodableWithKeypathWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.delete("/users", keypath: "users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - -} +//class DeletehRequestTests: XCTestCase { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// override func setUpWithError() throws { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +// func testDELETEVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.delete("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEVoidAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _: Void = try await network.delete("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// } +// +// func testDELETEDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEDataAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = try await network.delete("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// func testDELETEJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEJSONAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: Any = try await network.delete("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// XCTAssertEqual(data, expectedResponseData) +// } +// +// func testDELETENetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON: UserJSON = try await network.delete("/users/1") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// } +// +// func testDELETEArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEArrayOfDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let users: [UserJSON] = try await network.delete("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(users[0].firstname, "John") +// XCTAssertEqual(users[0].lastname, "Doe") +// XCTAssertEqual(users[1].firstname, "Jimmy") +// XCTAssertEqual(users[1].lastname, "Punchline") +// } +// +// func testDELETEArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +//} diff --git a/Tests/NetworkingTests/GetRequestTests.swift b/Tests/NetworkingTests/GetRequestTests.swift index 4d22f88..2108f75 100644 --- a/Tests/NetworkingTests/GetRequestTests.swift +++ b/Tests/NetworkingTests/GetRequestTests.swift @@ -5,324 +5,328 @@ // Created by Sacha DSO on 12/04/2022. // +import Testing import Foundation -import XCTest import Combine @testable import Networking -final class GetRequestTests: XCTestCase { +@Suite + +struct GetRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") private var cancellables = Set() - override func setUpWithError() throws { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - - func testGETVoidWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "Call works") - let expectationFinished = expectation(description: "Finished") - network.get("/users").sink { completion in - switch completion { - case .failure(_): - XCTFail() - case .finished: - expectationFinished.fulfill() - } - } receiveValue: { () in - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +// func testGETVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.get("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testGETVoidAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _:Void = try await network.get("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// } +// +// func testGETVoidAsyncWithURLParams() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// +// let _:Void = try await network.get("/users", params: ["search" : "lion"]) +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users?search=lion") +// } +// +// func testGETDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } - func testGETVoidAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _:Void = try await network.get("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - } - - func testGETVoidAsyncWithURLParams() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - - let _:Void = try await network.get("/users", params: ["search" : "lion"]) - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users?search=lion") - } - - func testGETDataWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.get("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - - } - } receiveValue: { (data: Data) in - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testGETDataAsyncWorks() async throws { + @Test + func GETDataAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ let data: Data = try await network.get("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) } - func testGETJSONWorks() { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.get("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (json: Any) in - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - - XCTAssertEqual(data, expectedResponseData) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testGETJSONAsyncWorks() async throws { +// func testGETJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// + @Test + func GETJSONAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ - let json: Any = try await network.get("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + let json: JSON = try await network.get("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") let expectedResponseData = """ {"response":"OK"} """.data(using: String.Encoding.utf8) - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - XCTAssertEqual(data, expectedResponseData) - } - - func testGETNetworkingJSONDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.get("/posts/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") - expectationFinished.fulfill() - } - } receiveValue: { (post: Post) in - XCTAssertEqual(post.title, "Hello") - XCTAssertEqual(post.content, "World") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testGETDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.get("/users/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: UserJSON) in - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testGETNetworkingJSONDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON: UserJSON = try await network.get("/posts/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - } - - func testGETArrayOfDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.get("/users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) + #expect(data == expectedResponseData) } - func testGETArrayOfDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let users: [UserJSON] = try await network.get("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") - } +// func testGETNetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// func testGETDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +//// +// func testGETNetworkingJSONDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON: UserJSON = try await network.get("/posts/1") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// } - func testGETArrayOfDecodableWithKeypathWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.get("/users", keypath: "users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } +// func testGETArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testGETArrayOfDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let users: [UserJSON] = try await network.get("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(users[0].firstname, "John") +// XCTAssertEqual(users[0].lastname, "Doe") +// XCTAssertEqual(users[1].firstname, "Jimmy") +// XCTAssertEqual(users[1].lastname, "Punchline") +// } +// +// +// func testGETArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } } diff --git a/Tests/NetworkingTests/MockingURLProtocol.swift b/Tests/NetworkingTests/MockingURLProtocol.swift index b2c55dc..03aab09 100644 --- a/Tests/NetworkingTests/MockingURLProtocol.swift +++ b/Tests/NetworkingTests/MockingURLProtocol.swift @@ -9,8 +9,8 @@ import Foundation class MockingURLProtocol: URLProtocol { - static var mockedResponse = "" - static var currentRequest: URLRequest? + nonisolated(unsafe) static var mockedResponse = "" + nonisolated(unsafe) static var currentRequest: URLRequest? override class func canInit(with request: URLRequest) -> Bool { currentRequest = request @@ -23,60 +23,60 @@ class MockingURLProtocol: URLProtocol { override func startLoading() { let data = MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8) - DispatchQueue.global(qos: .default).async { +// DispatchQueue.global(qos: .default).async { self.client?.urlProtocol(self, didLoad: data!) self.client?.urlProtocol(self, didReceive: URLResponse(), cacheStoragePolicy: URLCache.StoragePolicy.allowed) self.client?.urlProtocolDidFinishLoading(self) - } +// } } override func stopLoading() { } } - - -extension Data { - init(inputStream: InputStream) throws { - inputStream.open() - defer { inputStream.close() } - self.init() - let bufferSize = 512 - var readBuffer = [UInt8](repeating: 0, count: bufferSize) - while inputStream.hasBytesAvailable { - let readBytes = inputStream.read(&readBuffer, maxLength: bufferSize) - switch readBytes { - case 0: - break - case ..<0: - throw inputStream.streamError! - default: - append(readBuffer, count: readBytes) - } - } - } -} - - -extension URLRequest { - func httpBodyStreamAsData() -> Data? { - guard let httpBodyStream else { return nil } - do { - return try Data(inputStream: httpBodyStream) - } catch { - return nil - } - } - - func httpBodyStreamAsJSON() -> Any? { - guard let httpBodyStreamAsData = httpBodyStreamAsData() else { return nil } - do { - let json = try JSONSerialization.jsonObject(with: httpBodyStreamAsData) - return json - } catch { - return nil - } - } - - func httpBodyStreamAsDictionary() -> [String: Any] { - return httpBodyStreamAsJSON() as? [String: Any] ?? [:] - } -} +// +// +//extension Data { +// init(inputStream: InputStream) throws { +// inputStream.open() +// defer { inputStream.close() } +// self.init() +// let bufferSize = 512 +// var readBuffer = [UInt8](repeating: 0, count: bufferSize) +// while inputStream.hasBytesAvailable { +// let readBytes = inputStream.read(&readBuffer, maxLength: bufferSize) +// switch readBytes { +// case 0: +// break +// case ..<0: +// throw inputStream.streamError! +// default: +// append(readBuffer, count: readBytes) +// } +// } +// } +//} +// +// +//extension URLRequest { +// func httpBodyStreamAsData() -> Data? { +// guard let httpBodyStream else { return nil } +// do { +// return try Data(inputStream: httpBodyStream) +// } catch { +// return nil +// } +// } +// +// func httpBodyStreamAsJSON() -> Any? { +// guard let httpBodyStreamAsData = httpBodyStreamAsData() else { return nil } +// do { +// let json = try JSONSerialization.jsonObject(with: httpBodyStreamAsData) +// return json +// } catch { +// return nil +// } +// } +// +// func httpBodyStreamAsDictionary() -> [String: Any] { +// return httpBodyStreamAsJSON() as? [String: Any] ?? [:] +// } +//} diff --git a/Tests/NetworkingTests/MultipartRequestTests.swift b/Tests/NetworkingTests/MultipartRequestTests.swift index 8d014d3..d21c670 100644 --- a/Tests/NetworkingTests/MultipartRequestTests.swift +++ b/Tests/NetworkingTests/MultipartRequestTests.swift @@ -1,114 +1,114 @@ +//// +//// MultipartRequestTests.swift +//// +//// +//// Created by Jeff Barg on 7/22/20. +//// // -// MultipartRequestTests.swift -// +//import Foundation +//import XCTest +//import Combine // -// Created by Jeff Barg on 7/22/20. +//@testable +//import Networking // - -import Foundation -import XCTest -import Combine - -@testable -import Networking - -final class MultipartRequestTests: XCTestCase { - let baseClient: NetworkingClient = NetworkingClient(baseURL: "https://example.com/") - let route = "/api/test" - - func testRequestGenerationWithSingleFile() { - // Set up test - let params: Params = [:] - let multipartData = MultipartData(name: "test_name", - fileData: "test data".data(using: .utf8)!, - fileName: "file.txt", - mimeType: "text/plain") - - // Construct request - let request = baseClient.request(.post, route, params: params) - request.multipartData = [multipartData] - - if let urlRequest = request.buildURLRequest(), - let body = urlRequest.httpBody, - let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { - // Extract boundary from header - XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) - let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") - - // Test correct body construction - let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + - "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" - let actualBody = String(data: body, encoding: .utf8) - XCTAssertEqual(actualBody, expectedBody) - } else { - XCTFail("Properly-formed URL request was not constructed") - } - } - - func testRequestGenerationWithParams() { - // Set up test - let params: Params = ["test_name": "test_value"] - let multipartData = MultipartData(name: "test_name", - fileData: "test data".data(using: .utf8)!, - fileName: "file.txt", - mimeType: "text/plain") - - // Construct request - let request = baseClient.request(.post, route, params: params) - request.multipartData = [multipartData] - - if let urlRequest = request.buildURLRequest(), - let body = urlRequest.httpBody, - let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { - // Extract boundary from header - XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) - let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") - - // Test correct body construction - let expectedBody = "--\(boundary)\r\nContent-Disposition: " + - "form-data; name=\"test_name\"\r\n\r\ntest_value\r\n--\(boundary)\r\nContent-Disposition: form-data; " + - "name=\"test_name\"; filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" - let actualBody = String(data: body, encoding: .utf8) - XCTAssertEqual(actualBody, expectedBody) - } else { - XCTFail("Properly-formed URL request was not constructed") - } - } - - func testRequestGenerationWithMultipleFiles() { - // Set up test - let params: Params = [:] - let multipartData = [ - MultipartData(name: "test_name", - fileData: "test data".data(using: .utf8)!, - fileName: "file.txt", - mimeType: "text/plain"), - MultipartData(name: "second_name", - fileData: "another file".data(using: .utf8)!, - fileName: "file2.txt", - mimeType: "text/plain") - ] - - // Construct request - let request = baseClient.request(.post, route, params: params) - request.multipartData = multipartData - - if let urlRequest = request.buildURLRequest(), - let body = urlRequest.httpBody, - let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { - // Extract boundary from header - XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) - let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") - - // Test correct body construction - let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + - "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest " + - "data\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"second_name\"; " + - "filename=\"file2.txt\"\r\nContent-Type: text/plain\r\n\r\nanother file\r\n--\(boundary)--" - let actualBody = String(data: body, encoding: .utf8) - XCTAssertEqual(actualBody, expectedBody) - } else { - XCTFail("Properly-formed URL request was not constructed") - } - } -} +//final class MultipartRequestTests: XCTestCase { +// let baseClient: NetworkingClient = NetworkingClient(baseURL: "https://example.com/") +// let route = "/api/test" +// +// func testRequestGenerationWithSingleFile() { +// // Set up test +// let params: Params = [:] +// let multipartData = MultipartData(name: "test_name", +// fileData: "test data".data(using: .utf8)!, +// fileName: "file.txt", +// mimeType: "text/plain") +// +// // Construct request +// let request = baseClient.request(.post, route, params: params) +// request.multipartData = [multipartData] +// +// if let urlRequest = request.buildURLRequest(), +// let body = urlRequest.httpBody, +// let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { +// // Extract boundary from header +// XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) +// let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") +// +// // Test correct body construction +// let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + +// "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" +// let actualBody = String(data: body, encoding: .utf8) +// XCTAssertEqual(actualBody, expectedBody) +// } else { +// XCTFail("Properly-formed URL request was not constructed") +// } +// } +// +// func testRequestGenerationWithParams() { +// // Set up test +// let params: Params = ["test_name": "test_value"] +// let multipartData = MultipartData(name: "test_name", +// fileData: "test data".data(using: .utf8)!, +// fileName: "file.txt", +// mimeType: "text/plain") +// +// // Construct request +// let request = baseClient.request(.post, route, params: params) +// request.multipartData = [multipartData] +// +// if let urlRequest = request.buildURLRequest(), +// let body = urlRequest.httpBody, +// let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { +// // Extract boundary from header +// XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) +// let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") +// +// // Test correct body construction +// let expectedBody = "--\(boundary)\r\nContent-Disposition: " + +// "form-data; name=\"test_name\"\r\n\r\ntest_value\r\n--\(boundary)\r\nContent-Disposition: form-data; " + +// "name=\"test_name\"; filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" +// let actualBody = String(data: body, encoding: .utf8) +// XCTAssertEqual(actualBody, expectedBody) +// } else { +// XCTFail("Properly-formed URL request was not constructed") +// } +// } +// +// func testRequestGenerationWithMultipleFiles() { +// // Set up test +// let params: Params = [:] +// let multipartData = [ +// MultipartData(name: "test_name", +// fileData: "test data".data(using: .utf8)!, +// fileName: "file.txt", +// mimeType: "text/plain"), +// MultipartData(name: "second_name", +// fileData: "another file".data(using: .utf8)!, +// fileName: "file2.txt", +// mimeType: "text/plain") +// ] +// +// // Construct request +// let request = baseClient.request(.post, route, params: params) +// request.multipartData = multipartData +// +// if let urlRequest = request.buildURLRequest(), +// let body = urlRequest.httpBody, +// let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { +// // Extract boundary from header +// XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) +// let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") +// +// // Test correct body construction +// let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + +// "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest " + +// "data\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"second_name\"; " + +// "filename=\"file2.txt\"\r\nContent-Type: text/plain\r\n\r\nanother file\r\n--\(boundary)--" +// let actualBody = String(data: body, encoding: .utf8) +// XCTAssertEqual(actualBody, expectedBody) +// } else { +// XCTFail("Properly-formed URL request was not constructed") +// } +// } +//} diff --git a/Tests/NetworkingTests/NetworkingTests.swift b/Tests/NetworkingTests/NetworkingTests.swift index 0c8ff14..f90dbdf 100644 --- a/Tests/NetworkingTests/NetworkingTests.swift +++ b/Tests/NetworkingTests/NetworkingTests.swift @@ -1,31 +1,31 @@ -import XCTest -import Networking -import Combine - -final class NetworkingTests: XCTestCase { - - var cancellables = Set() - var cancellable: Cancellable? - - func testBadURLDoesntCrash() { - let exp = expectation(description: "call") - let client = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") - client.get("/forge a bad url") - .sink(receiveCompletion: { completion in - switch completion { - case .finished: - print("finished") - case .failure(let error): - if let e = error as? NetworkingError, e.status == .unableToParseRequest { - exp.fulfill() - } else { - exp.fulfill() - } - } - }, - receiveValue: { (json: Any) in - print(json) - }).store(in: &cancellables) - waitForExpectations(timeout: 1, handler: nil) - } -} +//import XCTest +//import Networking +//import Combine +// +//final class NetworkingTests: XCTestCase { +// +// var cancellables = Set() +// var cancellable: Cancellable? +// +// func testBadURLDoesntCrash() { +// let exp = expectation(description: "call") +// let client = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") +// client.get("/forge a bad url") +// .sink(receiveCompletion: { completion in +// switch completion { +// case .finished: +// print("finished") +// case .failure(let error): +// if let e = error as? NetworkingError, e.status == .unableToParseRequest { +// exp.fulfill() +// } else { +// exp.fulfill() +// } +// } +// }, +// receiveValue: { (json: Any) in +// print(json) +// }).store(in: &cancellables) +// waitForExpectations(timeout: 1, handler: nil) +// } +//} diff --git a/Tests/NetworkingTests/PatchRequestTests.swift b/Tests/NetworkingTests/PatchRequestTests.swift index 7eb8e81..09e3f07 100644 --- a/Tests/NetworkingTests/PatchRequestTests.swift +++ b/Tests/NetworkingTests/PatchRequestTests.swift @@ -1,316 +1,316 @@ +//// +//// PatchRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// PatchRequestTests.swift -// +//import Foundation +//import XCTest +//import Combine // -// Created by Sacha DSO on 12/04/2022. +//@testable +//import Networking // - -import Foundation -import XCTest -import Combine - -@testable -import Networking - -class PatchRequestTests: XCTestCase { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - override func setUpWithError() throws { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - - func testPATCHVoidWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "Call works") - let expectationFinished = expectation(description: "Finished") - network.patch("/users").sink { completion in - switch completion { - case .failure(_): - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { () in - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPATCHVoidAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _:Void = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - } - - func testPATCHDataWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.patch("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (data: Data) in - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPATCHDataAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - func testPATCHJSONWorks() { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.patch("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (json: Any) in - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - - XCTAssertEqual(data, expectedResponseData) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPATCHJSONAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: Any = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - XCTAssertEqual(data, expectedResponseData) - } - - func testPATCHNetworkingJSONDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.patch("/posts/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") - expectationFinished.fulfill() - } - } receiveValue: { (post: Post) in - XCTAssertEqual(post.title, "Hello") - XCTAssertEqual(post.content, "World") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPATCHDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.patch("/users/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: UserJSON) in - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPATCHDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let user: UserJSON = try await network.patch("/users/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - XCTAssertEqual(user.firstname, "John") - XCTAssertEqual(user.lastname, "Doe") - } - - func testPATCHArrayOfDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.patch("/users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPATCHArrayOfDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let users: [UserJSON] = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") - } - - func testPATCHArrayOfDecodableWithKeypathWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.patch("/users", keypath: "users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - -} +//class PatchRequestTests: XCTestCase { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// override func setUpWithError() throws { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +// func testPATCHVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.patch("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPATCHVoidAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _:Void = try await network.patch("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// } +// +// func testPATCHDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.patch("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPATCHDataAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = try await network.patch("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// func testPATCHJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.patch("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPATCHJSONAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: Any = try await network.patch("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// XCTAssertEqual(data, expectedResponseData) +// } +// +// func testPATCHNetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.patch("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPATCHDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.patch("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPATCHDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let user: UserJSON = try await network.patch("/users/1") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// XCTAssertEqual(user.firstname, "John") +// XCTAssertEqual(user.lastname, "Doe") +// } +// +// func testPATCHArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.patch("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPATCHArrayOfDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let users: [UserJSON] = try await network.patch("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(users[0].firstname, "John") +// XCTAssertEqual(users[0].lastname, "Doe") +// XCTAssertEqual(users[1].firstname, "Jimmy") +// XCTAssertEqual(users[1].lastname, "Punchline") +// } +// +// func testPATCHArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.patch("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +//} diff --git a/Tests/NetworkingTests/PostRequestTests.swift b/Tests/NetworkingTests/PostRequestTests.swift index efb7243..4859f04 100644 --- a/Tests/NetworkingTests/PostRequestTests.swift +++ b/Tests/NetworkingTests/PostRequestTests.swift @@ -1,369 +1,369 @@ +//// +//// PostRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// PostRequestTests.swift -// +//import Foundation +//import XCTest +//import Combine // -// Created by Sacha DSO on 12/04/2022. +//@testable +//import Networking // - -import Foundation -import XCTest -import Combine - -@testable -import Networking - -class PostRequestTests: XCTestCase { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - override func setUpWithError() throws { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - - func testPOSTVoidWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "Call works") - let expectationFinished = expectation(description: "Finished") - network.post("/users").sink { completion in - switch completion { - case .failure(_): - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { () in - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPOSTVoidAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _: Void = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - } - - func testPOSTDataWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.post("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (data: Data) in - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPOSTDataAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - func testPOSTJSONWorks() { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.post("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (json: Any) in - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - - XCTAssertEqual(data, expectedResponseData) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPOSTJSONAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: Any = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - XCTAssertEqual(data, expectedResponseData) - } - - func testPOSTNetworkingJSONDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.post("/posts/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") - expectationFinished.fulfill() - } - } receiveValue: { (post: Post) in - XCTAssertEqual(post.title, "Hello") - XCTAssertEqual(post.content, "World") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPOSTDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.post("/users/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: UserJSON) in - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPOSTDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON:UserJSON = try await network.post("/users/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - } - - func testPOSTArrayOfDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.post("/users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPOSTArrayOfDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let users: [UserJSON] = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") - } - - func testPOSTArrayOfDecodableWithKeypathWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.post("/users", keypath: "users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testAsyncPostEncodable() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - - let creds = Credentials(username: "john", password: "doe") - let data: Data = try await network.post("/users", body: creds) - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - - let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() - XCTAssertEqual(body?["username"] as? String, "john") - XCTAssertEqual(body?["password"] as? String, "doe") - } - - func testPOSTDataEncodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - - let creds = Credentials(username: "Alan", password: "Turing") - network.post("/users", body: creds).sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - - let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() - XCTAssertEqual(body?["username"] as? String, "Alan") - XCTAssertEqual(body?["password"] as? String, "Turing") - - expectationFinished.fulfill() - } - } receiveValue: { (data: Data) in - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - -} - -struct Credentials: Encodable { - let username: String - let password: String -} +//class PostRequestTests: XCTestCase { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// override func setUpWithError() throws { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +// func testPOSTVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.post("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTVoidAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _: Void = try await network.post("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// } +// +// func testPOSTDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTDataAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = try await network.post("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// func testPOSTJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTJSONAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: Any = try await network.post("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// XCTAssertEqual(data, expectedResponseData) +// } +// +// func testPOSTNetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON:UserJSON = try await network.post("/users/1") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// } +// +// func testPOSTArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTArrayOfDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let users: [UserJSON] = try await network.post("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(users[0].firstname, "John") +// XCTAssertEqual(users[0].lastname, "Doe") +// XCTAssertEqual(users[1].firstname, "Jimmy") +// XCTAssertEqual(users[1].lastname, "Punchline") +// } +// +// func testPOSTArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testAsyncPostEncodable() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// +// let creds = Credentials(username: "john", password: "doe") +// let data: Data = try await network.post("/users", body: creds) +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// +// let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() +// XCTAssertEqual(body?["username"] as? String, "john") +// XCTAssertEqual(body?["password"] as? String, "doe") +// } +// +// func testPOSTDataEncodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// +// let creds = Credentials(username: "Alan", password: "Turing") +// network.post("/users", body: creds).sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// +// let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() +// XCTAssertEqual(body?["username"] as? String, "Alan") +// XCTAssertEqual(body?["password"] as? String, "Turing") +// +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +//} +// +//struct Credentials: Encodable { +// let username: String +// let password: String +//} diff --git a/Tests/NetworkingTests/PutRequestTests.swift b/Tests/NetworkingTests/PutRequestTests.swift index 18eb3b3..41797dc 100644 --- a/Tests/NetworkingTests/PutRequestTests.swift +++ b/Tests/NetworkingTests/PutRequestTests.swift @@ -1,316 +1,316 @@ +//// +//// PutRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// PutRequestTests.swift +//import Foundation +//import XCTest +//import Combine // +//@testable +//import Networking // -// Created by Sacha DSO on 12/04/2022. +//class PutRequestTests: XCTestCase { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() // - -import Foundation -import XCTest -import Combine - -@testable -import Networking - -class PutRequestTests: XCTestCase { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - override func setUpWithError() throws { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - - func testPUTVoidWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "Call works") - let expectationFinished = expectation(description: "Finished") - network.put("/users").sink { completion in - switch completion { - case .failure(_): - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { () in - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPUTVoidAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _: Void = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - } - - func testPUTDataWorks() { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.put("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (data: Data) in - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPUTDataAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - func testPUTJSONWorks() { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.put("/users").sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (json: Any) in - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - - XCTAssertEqual(data, expectedResponseData) - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPUTJSONAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: Any = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - XCTAssertEqual(data, expectedResponseData) - } - - func testPUTNetworkingJSONDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.put("/posts/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") - expectationFinished.fulfill() - } - } receiveValue: { (post: Post) in - XCTAssertEqual(post.title, "Hello") - XCTAssertEqual(post.content, "World") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPUTDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.put("/users/1") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: UserJSON) in - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPUTDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let user: UserJSON = try await network.put("/users/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - XCTAssertEqual(user.firstname, "John") - XCTAssertEqual(user.lastname, "Doe") - } - - func testPUTArrayOfDecodableWorks() { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.put("/users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - - func testPUTArrayOfDecodableAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let users: [UserJSON] = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") - } - - func testPUTArrayOfDecodableWithKeypathWorks() { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let expectationWorks = expectation(description: "ReceiveValue called") - let expectationFinished = expectation(description: "Finished called") - network.put("/users", keypath: "users") - .sink { completion in - switch completion { - case .failure: - XCTFail() - case .finished: - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - expectationFinished.fulfill() - } - } receiveValue: { (userJSON: [UserJSON]) in - XCTAssertEqual(userJSON[0].firstname, "John") - XCTAssertEqual(userJSON[0].lastname, "Doe") - XCTAssertEqual(userJSON[1].firstname, "Jimmy") - XCTAssertEqual(userJSON[1].lastname, "Punchline") - expectationWorks.fulfill() - } - .store(in: &cancellables) - waitForExpectations(timeout: 0.1) - } - -} +// override func setUpWithError() throws { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +// func testPUTVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.put("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPUTVoidAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _: Void = try await network.put("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// } +// +// func testPUTDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPUTDataAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = try await network.put("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// func testPUTJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPUTJSONAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: Any = try await network.put("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// XCTAssertEqual(data, expectedResponseData) +// } +// +// func testPUTNetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPUTDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPUTDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let user: UserJSON = try await network.put("/users/1") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// XCTAssertEqual(user.firstname, "John") +// XCTAssertEqual(user.lastname, "Doe") +// } +// +// func testPUTArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPUTArrayOfDecodableAsyncWorks() async throws { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let users: [UserJSON] = try await network.put("/users") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// XCTAssertEqual(users[0].firstname, "John") +// XCTAssertEqual(users[0].lastname, "Doe") +// XCTAssertEqual(users[1].firstname, "Jimmy") +// XCTAssertEqual(users[1].lastname, "Punchline") +// } +// +// func testPUTArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +//} From 632e4d06ffc9cbf372452e1d600be3db25429b36 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 25 Sep 2024 13:48:04 -1000 Subject: [PATCH 06/25] Update GetRequestTests.swift --- Tests/NetworkingTests/GetRequestTests.swift | 125 ++++++++++---------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/Tests/NetworkingTests/GetRequestTests.swift b/Tests/NetworkingTests/GetRequestTests.swift index 2108f75..f6235ca 100644 --- a/Tests/NetworkingTests/GetRequestTests.swift +++ b/Tests/NetworkingTests/GetRequestTests.swift @@ -12,8 +12,7 @@ import Combine @testable import Networking -@Suite - +@Suite(.serialized) struct GetRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") @@ -48,27 +47,29 @@ struct GetRequestTests { // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -// func testGETVoidAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let _:Void = try await network.get("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// } -// -// func testGETVoidAsyncWithURLParams() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// -// let _:Void = try await network.get("/users", params: ["search" : "lion"]) -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users?search=lion") -// } +// + @Test + func GETVoidAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _:Void = try await network.get("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + + @Test + func GETVoidAsyncWithURLParams() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + + let _:Void = try await network.get("/users", params: ["search" : "lion"]) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users?search=lion") + } // // func testGETDataWorks() { // MockingURLProtocol.mockedResponse = @@ -212,21 +213,22 @@ struct GetRequestTests { // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -//// -// func testGETNetworkingJSONDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let userJSON: UserJSON = try await network.get("/posts/1") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// } +//// + @Test + func GETNetworkingJSONDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let userJSON: UserJSON = try await network.get("/posts/1") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") + } // func testGETArrayOfDecodableWorks() { // MockingURLProtocol.mockedResponse = @@ -264,29 +266,30 @@ struct GetRequestTests { // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -// func testGETArrayOfDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let users: [UserJSON] = try await network.get("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(users[0].firstname, "John") -// XCTAssertEqual(users[0].lastname, "Doe") -// XCTAssertEqual(users[1].firstname, "Jimmy") -// XCTAssertEqual(users[1].lastname, "Punchline") -// } +// + @Test + func GETArrayOfDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let users: [UserJSON] = try await network.get("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(users[0].firstname == "John") + #expect(users[0].lastname == "Doe") + #expect(users[1].firstname == "Jimmy") + #expect(users[1].lastname == "Punchline") + } // // // func testGETArrayOfDecodableWithKeypathWorks() { From 4ad06d2386b711aa12f7cc3ff46b52b86f7991fa Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Thu, 26 Sep 2024 07:59:17 -1000 Subject: [PATCH 07/25] WIP - Separate Combine Api from async await one --- Package.swift | 1 + Sources/Networking/Calls/JSON.swift | 41 ++++ .../Calls/NetworkingClient+Data.swift | 32 --- .../Calls/NetworkingClient+Decodable.swift | 126 ---------- .../Calls/NetworkingClient+JSON.swift | 76 ------ .../Calls/NetworkingClient+Requests.swift | 3 +- .../Calls/NetworkingClient+Void.swift | 46 ---- .../Calls/NetworkingJSONDecodable.swift | 13 + .../NetworkingClient+Data+Combine.swift | 40 +++ .../NetworkingClient+Decodable+Combine.swift | 133 ++++++++++ .../NetworkingClient+JSON+Combine.swift | 54 +++++ .../NetworkingClient+Multipart.swift | 0 ...orkingClient+NetworkingJSONDecodable.swift | 6 - .../NetworkingClient+Void+Combine.swift | 54 +++++ .../Combine/NetworkingRequest+Publisher.swift | 101 ++++++++ .../Combine/NetworkingService+Combine.swift | 227 ++++++++++++++++++ Sources/Networking/NetworkingClient.swift | 14 +- .../NetworkingRequest+Execute.swift | 29 +++ Sources/Networking/NetworkingRequest.swift | 141 +---------- Sources/Networking/NetworkingService.swift | 217 ----------------- 20 files changed, 711 insertions(+), 643 deletions(-) create mode 100644 Sources/Networking/Calls/JSON.swift create mode 100644 Sources/Networking/Calls/NetworkingJSONDecodable.swift create mode 100644 Sources/Networking/Combine/NetworkingClient+Data+Combine.swift create mode 100644 Sources/Networking/Combine/NetworkingClient+Decodable+Combine.swift create mode 100644 Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift rename Sources/Networking/{Calls => Combine}/NetworkingClient+Multipart.swift (100%) rename Sources/Networking/{Calls => Combine}/NetworkingClient+NetworkingJSONDecodable.swift (97%) create mode 100644 Sources/Networking/Combine/NetworkingClient+Void+Combine.swift create mode 100644 Sources/Networking/Combine/NetworkingRequest+Publisher.swift create mode 100644 Sources/Networking/Combine/NetworkingService+Combine.swift create mode 100644 Sources/Networking/NetworkingRequest+Execute.swift diff --git a/Package.swift b/Package.swift index 05eaa1c..572b660 100644 --- a/Package.swift +++ b/Package.swift @@ -12,3 +12,4 @@ let package = Package( .testTarget(name: "NetworkingTests", dependencies: ["Networking"]) ] ) +// TODO handle retries diff --git a/Sources/Networking/Calls/JSON.swift b/Sources/Networking/Calls/JSON.swift new file mode 100644 index 0000000..5f45ead --- /dev/null +++ b/Sources/Networking/Calls/JSON.swift @@ -0,0 +1,41 @@ +// +// JSON.swift +// Networking +// +// Created by Sacha Durand Saint Omer on 26/09/2024. +// + +import Foundation + +public struct JSON: Sendable, CustomStringConvertible { + + let array: [any Sendable]? + let dictionary: [String: any Sendable]? + + init(jsonObject: Any) { + if let arr = jsonObject as? [Sendable] { + array = arr + dictionary = nil + } else if let dic = jsonObject as? [String: any Sendable] { + dictionary = dic + array = nil + } else { + array = nil + dictionary = nil + } + } + + var value: Any { + return array ?? dictionary ?? "" + } + + public var description: String { + if let array = array { + return String(describing: array) + } else if let dictionary = dictionary { + return String(describing: dictionary) + } + return "empty" + } + +} diff --git a/Sources/Networking/Calls/NetworkingClient+Data.swift b/Sources/Networking/Calls/NetworkingClient+Data.swift index 356a745..c5d6ce2 100644 --- a/Sources/Networking/Calls/NetworkingClient+Data.swift +++ b/Sources/Networking/Calls/NetworkingClient+Data.swift @@ -6,38 +6,6 @@ // import Foundation -import Combine - -public extension NetworkingClient { - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.get, route, params: params)) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.post, route, params: params)) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - publisher(request: request(.post, route, encodableBody: body)) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.put, route, params: params)) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.patch, route, params: params)) - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - publisher(request: request(.patch, route, encodableBody: body)) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.delete, route, params: params)) - } -} public extension NetworkingClient { diff --git a/Sources/Networking/Calls/NetworkingClient+Decodable.swift b/Sources/Networking/Calls/NetworkingClient+Decodable.swift index fc53093..5765d66 100644 --- a/Sources/Networking/Calls/NetworkingClient+Decodable.swift +++ b/Sources/Networking/Calls/NetworkingClient+Decodable.swift @@ -6,132 +6,6 @@ // import Foundation -import Combine - -public extension NetworkingClient { - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - return get(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return get(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - return post(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func post(_ route: String, - body: Encodable, - keypath: String? = nil - ) -> AnyPublisher { - return post(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return post(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - return put(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return put(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - return patch(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - - func patch(_ route: String, - body: Encodable, - keypath: String? = nil - ) -> AnyPublisher { - return patch(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return patch(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - return delete(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return delete(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } -} - public extension NetworkingClient { diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift index f7e57ec..242e743 100644 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ b/Sources/Networking/Calls/NetworkingClient+JSON.swift @@ -6,38 +6,6 @@ // import Foundation -import Combine - -public extension NetworkingClient { - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - get(route, params: params).toJSON() - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - post(route, params: params).toJSON() - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - post(route, body: body).toJSON() - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - put(route, params: params).toJSON() - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - patch(route, params: params).toJSON() - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - patch(route, body: body).toJSON() - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - delete(route, params: params).toJSON() - } -} public extension NetworkingClient { @@ -91,47 +59,3 @@ public extension NetworkingClient { } } -// Data to JSON -extension Publisher where Output == Data { - - public func toJSON() -> AnyPublisher { - tryMap { data -> JSON in - let json = try JSONSerialization.jsonObject(with: data, options: []) - return JSON(jsonObject: json) - }.eraseToAnyPublisher() - } -} - - -public struct JSON: Sendable, CustomStringConvertible { - - let array: [any Sendable]? - let dictionary: [String: any Sendable]? - - init(jsonObject: Any) { - if let arr = jsonObject as? [Sendable] { - array = arr - dictionary = nil - } else if let dic = jsonObject as? [String: any Sendable] { - dictionary = dic - array = nil - } else { - array = nil - dictionary = nil - } - } - - var value: Any { - return array ?? dictionary ?? "" - } - - public var description: String { - if let array = array { - return String(describing: array) - } else if let dictionary = dictionary { - return String(describing: dictionary) - } - return "empty" - } - -} diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index e09e1e4..77c0892 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -6,7 +6,6 @@ // import Foundation -import Combine public extension NetworkingClient { @@ -65,4 +64,4 @@ public extension NetworkingClient { } -// TODO handle retries + diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift index 6a9f32b..be814f5 100644 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ b/Sources/Networking/Calls/NetworkingClient+Void.swift @@ -6,52 +6,6 @@ // import Foundation -import Combine - -public extension NetworkingClient { - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - get(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - post(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - post(route, body: body) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - put(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - patch(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - patch(route, body: body) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - delete(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } -} public extension NetworkingClient { diff --git a/Sources/Networking/Calls/NetworkingJSONDecodable.swift b/Sources/Networking/Calls/NetworkingJSONDecodable.swift new file mode 100644 index 0000000..8cf0b1d --- /dev/null +++ b/Sources/Networking/Calls/NetworkingJSONDecodable.swift @@ -0,0 +1,13 @@ +// +// NetworkingJSONDecodable.swift +// Networking +// +// Created by Sacha Durand Saint Omer on 26/09/2024. +// + +import Foundation + +public protocol NetworkingJSONDecodable { + /// The method you declare your JSON mapping in. + static func decode(_ json: Any) throws -> Self +} diff --git a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift new file mode 100644 index 0000000..f6822d4 --- /dev/null +++ b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift @@ -0,0 +1,40 @@ +// +// NetworkingClient+Data.swift +// +// +// Created by Sacha on 13/03/2020. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func get(_ route: String, params: Params = Params()) -> AnyPublisher { + publisher(request: request(.get, route, params: params)) + } + + func post(_ route: String, params: Params = Params()) -> AnyPublisher { + publisher(request: request(.post, route, params: params)) + } + + func post(_ route: String, body: Encodable) -> AnyPublisher { + publisher(request: request(.post, route, encodableBody: body)) + } + + func put(_ route: String, params: Params = Params()) -> AnyPublisher { + publisher(request: request(.put, route, params: params)) + } + + func patch(_ route: String, params: Params = Params()) -> AnyPublisher { + publisher(request: request(.patch, route, params: params)) + } + + func patch(_ route: String, body: Encodable) -> AnyPublisher { + publisher(request: request(.patch, route, encodableBody: body)) + } + + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + publisher(request: request(.delete, route, params: params)) + } +} diff --git a/Sources/Networking/Combine/NetworkingClient+Decodable+Combine.swift b/Sources/Networking/Combine/NetworkingClient+Decodable+Combine.swift new file mode 100644 index 0000000..b75610f --- /dev/null +++ b/Sources/Networking/Combine/NetworkingClient+Decodable+Combine.swift @@ -0,0 +1,133 @@ +// +// NetworkingClient+Decodable.swift +// +// +// Created by Sacha DSO on 12/04/2022. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func get(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + return get(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func get(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return get(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func post(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + return post(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func post(_ route: String, + body: Encodable, + keypath: String? = nil + ) -> AnyPublisher { + return post(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func post(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return post(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func put(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + return put(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func put(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return put(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func patch(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + return patch(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + + func patch(_ route: String, + body: Encodable, + keypath: String? = nil + ) -> AnyPublisher { + return patch(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func patch(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return patch(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func delete(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + return delete(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func delete(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return delete(route, params: params) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } +} diff --git a/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift b/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift new file mode 100644 index 0000000..e234a83 --- /dev/null +++ b/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift @@ -0,0 +1,54 @@ +// +// NetworkingClient+JSON.swift +// +// +// Created by Sacha on 13/03/2020. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func get(_ route: String, params: Params = Params()) -> AnyPublisher { + get(route, params: params).toJSON() + } + + func post(_ route: String, params: Params = Params()) -> AnyPublisher { + post(route, params: params).toJSON() + } + + func post(_ route: String, body: Encodable) -> AnyPublisher { + post(route, body: body).toJSON() + } + + func put(_ route: String, params: Params = Params()) -> AnyPublisher { + put(route, params: params).toJSON() + } + + func patch(_ route: String, params: Params = Params()) -> AnyPublisher { + patch(route, params: params).toJSON() + } + + func patch(_ route: String, body: Encodable) -> AnyPublisher { + patch(route, body: body).toJSON() + } + + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + delete(route, params: params).toJSON() + } +} + + + +// Data to JSON +extension Publisher where Output == Data { + + public func toJSON() -> AnyPublisher { + tryMap { data -> JSON in + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSON(jsonObject: json) + }.eraseToAnyPublisher() + } +} + diff --git a/Sources/Networking/Calls/NetworkingClient+Multipart.swift b/Sources/Networking/Combine/NetworkingClient+Multipart.swift similarity index 100% rename from Sources/Networking/Calls/NetworkingClient+Multipart.swift rename to Sources/Networking/Combine/NetworkingClient+Multipart.swift diff --git a/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift b/Sources/Networking/Combine/NetworkingClient+NetworkingJSONDecodable.swift similarity index 97% rename from Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift rename to Sources/Networking/Combine/NetworkingClient+NetworkingJSONDecodable.swift index 68944be..e51f0b9 100644 --- a/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift +++ b/Sources/Networking/Combine/NetworkingClient+NetworkingJSONDecodable.swift @@ -8,12 +8,6 @@ import Foundation import Combine - -public protocol NetworkingJSONDecodable { - /// The method you declare your JSON mapping in. - static func decode(_ json: Any) throws -> Self -} - public extension NetworkingClient { func get(_ route: String, diff --git a/Sources/Networking/Combine/NetworkingClient+Void+Combine.swift b/Sources/Networking/Combine/NetworkingClient+Void+Combine.swift new file mode 100644 index 0000000..71d7ade --- /dev/null +++ b/Sources/Networking/Combine/NetworkingClient+Void+Combine.swift @@ -0,0 +1,54 @@ +// +// NetworkingClient+Void.swift +// +// +// Created by Sacha on 13/03/2020. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func get(_ route: String, params: Params = Params()) -> AnyPublisher { + get(route, params: params) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func post(_ route: String, params: Params = Params()) -> AnyPublisher { + post(route, params: params) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func post(_ route: String, body: Encodable) -> AnyPublisher { + post(route, body: body) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func put(_ route: String, params: Params = Params()) -> AnyPublisher { + put(route, params: params) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func patch(_ route: String, params: Params = Params()) -> AnyPublisher { + patch(route, params: params) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func patch(_ route: String, body: Encodable) -> AnyPublisher { + patch(route, body: body) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + delete(route, params: params) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } +} diff --git a/Sources/Networking/Combine/NetworkingRequest+Publisher.swift b/Sources/Networking/Combine/NetworkingRequest+Publisher.swift new file mode 100644 index 0000000..fe742a6 --- /dev/null +++ b/Sources/Networking/Combine/NetworkingRequest+Publisher.swift @@ -0,0 +1,101 @@ +// +// NetworkingRequest+Execute.swift +// +// +// Created by Sacha DSO on 26/09/2024. +// + +import Foundation +@preconcurrency import Combine + + +extension NetworkingClient { + + public func uploadPublisher(request: NetworkingRequest) -> AnyPublisher<(Data?, Progress), Error> { + + guard let urlRequest = request.buildURLRequest() else { + return Fail(error: NetworkingError.unableToParseRequest as Error) + .eraseToAnyPublisher() + } + logger.log(request: urlRequest) + + let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) + let callPublisher: AnyPublisher<(Data?, Progress), Error> = urlSession.dataTaskPublisher(for: urlRequest) + .tryMap { (data: Data, response: URLResponse) -> Data in + self.logger.log(response: response, data: data) + if let httpURLResponse = response as? HTTPURLResponse { + if !(200...299 ~= httpURLResponse.statusCode) { + var error = NetworkingError(errorCode: httpURLResponse.statusCode) + if let json = try? JSONSerialization.jsonObject(with: data, options: []) { + error.jsonPayload = JSON(jsonObject: json) + } + throw error + } + } + return data + }.mapError { error -> NetworkingError in + return NetworkingError(error: error) + }.map { data -> (Data?, Progress) in + return (data, Progress()) + }.eraseToAnyPublisher() + + return callPublisher + .eraseToAnyPublisher() + // Todo put back progress +// +// let progressPublisher2: AnyPublisher<(Data?, Progress), Error> = sessionDelegate.progressPublisher +// .map { progress -> (Data?, Progress) in +// return (nil, progress) +// }.eraseToAnyPublisher() +// +// return Publishers.Merge(callPublisher, progressPublisher2) +// .receive(on: DispatchQueue.main) +// .eraseToAnyPublisher() + } + + public func publisher(request: NetworkingRequest) -> AnyPublisher { + publisher(request: request, retryCount: request.maxRetryCount) + } + + private func publisher(request: NetworkingRequest, retryCount: Int) -> AnyPublisher { + guard let urlRequest = request.buildURLRequest() else { + return Fail(error: NetworkingError.unableToParseRequest as Error) + .eraseToAnyPublisher() + } + logger.log(request: urlRequest) + + let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) + return urlSession.dataTaskPublisher(for: urlRequest) + .tryMap { (data: Data, response: URLResponse) -> Data in + self.logger.log(response: response, data: data) + if let httpURLResponse = response as? HTTPURLResponse { + if !(200...299 ~= httpURLResponse.statusCode) { + var error = NetworkingError(errorCode: httpURLResponse.statusCode) + if let json = try? JSONSerialization.jsonObject(with: data, options: []) { + error.jsonPayload = JSON(jsonObject: json) + } + throw error + } + } + return data + } + // TODO fix retry +// .tryCatch({ [weak self, urlRequest] error -> AnyPublisher in +// guard +// let self = self, +// retryCount > 1, +// let retryPublisher = self.requestRetrier?(urlRequest, error) +// else { +// throw error +// } +// return retryPublisher +// .flatMap { _ -> AnyPublisher in +// self.publisher(request: request, retryCount: retryCount - 1) +// } +// .eraseToAnyPublisher() +// }) + .mapError { error -> NetworkingError in + return NetworkingError(error: error) + }.receive(on: DispatchQueue.main).eraseToAnyPublisher() + } +} diff --git a/Sources/Networking/Combine/NetworkingService+Combine.swift b/Sources/Networking/Combine/NetworkingService+Combine.swift new file mode 100644 index 0000000..cd6f6e1 --- /dev/null +++ b/Sources/Networking/Combine/NetworkingService+Combine.swift @@ -0,0 +1,227 @@ +// +// NetworkingService.swift +// +// +// Created by Sacha on 13/03/2020. +// + +import Foundation +@preconcurrency import Combine + +// Sugar, just forward calls to underlying network client + +public extension NetworkingService { + + // Data + +// func get(_ route: String, params: Params = Params()) async -> AnyPublisher { +// await network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) async -> AnyPublisher { +// await network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable & Sendable) async -> AnyPublisher { +// await network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func patch(_ route: String, body: Encodable) -> AnyPublisher { +// network.patch(route, body: body) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // Void +// +// func get(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // JSON +// +// func get(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // Decodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.delete(route, params: params, keypath: keypath) +// } +// +// // Array Decodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.delete(route, params: params, keypath: keypath) +// } +// +// // NetworkingJSONDecodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.delete(route, params: params, keypath: keypath) +// } +// +// +// +// // Array NetworkingJSONDecodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.delete(route, params: params, keypath: keypath) +// } +} + diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index 41e6c10..4e1278a 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -1,9 +1,9 @@ import Foundation -import Combine +//import Combine actor NetworkingClientURLSessionDelegate: NSObject, URLSessionDelegate { - let progressPublisher = PassthroughSubject() +// let progressPublisher = PassthroughSubject() public func urlSession(_ session: URLSession, task: URLSessionTask, @@ -12,11 +12,11 @@ actor NetworkingClientURLSessionDelegate: NSObject, URLSessionDelegate { totalBytesExpectedToSend: Int64) { let progress = Progress(totalUnitCount: totalBytesExpectedToSend) progress.completedUnitCount = totalBytesSent - progressPublisher.send(progress) +// progressPublisher.send(progress) } } -public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? +// public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? public actor NetworkingClient { /** @@ -31,7 +31,7 @@ public actor NetworkingClient { public var parameterEncoding = ParameterEncoding.urlEncoded public var timeout: TimeInterval? public var sessionConfiguration = URLSessionConfiguration.default - public var requestRetrier: NetworkRequestRetrier? +// public var requestRetrier: NetworkRequestRetrier? public var jsonDecoderFactory: (() -> JSONDecoder)? let sessionDelegate = NetworkingClientURLSessionDelegate() @@ -109,7 +109,3 @@ public actor NetworkingClient { return json.value } } - -extension NetworkingClient { - -} diff --git a/Sources/Networking/NetworkingRequest+Execute.swift b/Sources/Networking/NetworkingRequest+Execute.swift new file mode 100644 index 0000000..25a6b90 --- /dev/null +++ b/Sources/Networking/NetworkingRequest+Execute.swift @@ -0,0 +1,29 @@ +// +// NetworkingRequest+Execute.swift +// +// +// Created by Sacha DSO on 26/09/2024. +// + +import Foundation + +extension NetworkingClient { + + func execute(request: NetworkingRequest) async throws -> Data { + guard let urlRequest = request.buildURLRequest() else { + throw NetworkingError.unableToParseRequest + } + logger.log(request: urlRequest) + let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) + let (data, response) = try await urlSession.data(for: urlRequest) + logger.log(response: response, data: data) + if let httpURLResponse = response as? HTTPURLResponse, !(200...299 ~= httpURLResponse.statusCode) { + var error = NetworkingError(errorCode: httpURLResponse.statusCode) + if let json = try? JSONSerialization.jsonObject(with: data, options: []) { + error.jsonPayload = JSON(jsonObject: json) + } + throw error + } + return data + } +} diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index 22c5479..48bbff9 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -6,11 +6,6 @@ // import Foundation -@preconcurrency import Combine - - - - public struct NetworkingRequest { let method: HTTPMethod @@ -22,138 +17,14 @@ public struct NetworkingRequest { let multipartData: [MultipartData]? let timeout: TimeInterval? let maxRetryCount = 3 -// var logLevel: NetworkingLogLevel { -// get { return logger.logLevel } -// set { logger.logLevel = newValue } -// } -} - -extension NetworkingClient { - - public func uploadPublisher(request: NetworkingRequest) -> AnyPublisher<(Data?, Progress), Error> { - - guard let urlRequest = request.buildURLRequest() else { - return Fail(error: NetworkingError.unableToParseRequest as Error) - .eraseToAnyPublisher() - } - logger.log(request: urlRequest) - - let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) - let callPublisher: AnyPublisher<(Data?, Progress), Error> = urlSession.dataTaskPublisher(for: urlRequest) - .tryMap { (data: Data, response: URLResponse) -> Data in - self.logger.log(response: response, data: data) - if let httpURLResponse = response as? HTTPURLResponse { - if !(200...299 ~= httpURLResponse.statusCode) { - var error = NetworkingError(errorCode: httpURLResponse.statusCode) - if let json = try? JSONSerialization.jsonObject(with: data, options: []) { - error.jsonPayload = JSON(jsonObject: json) - } - throw error - } - } - return data - }.mapError { error -> NetworkingError in - return NetworkingError(error: error) - }.map { data -> (Data?, Progress) in - return (data, Progress()) - }.eraseToAnyPublisher() - - return callPublisher - .eraseToAnyPublisher() - // Todo put back progress -// -// let progressPublisher2: AnyPublisher<(Data?, Progress), Error> = sessionDelegate.progressPublisher -// .map { progress -> (Data?, Progress) in -// return (nil, progress) -// }.eraseToAnyPublisher() -// -// return Publishers.Merge(callPublisher, progressPublisher2) -// .receive(on: DispatchQueue.main) -// .eraseToAnyPublisher() - } - - public func publisher(request: NetworkingRequest) -> AnyPublisher { - publisher(request: request, retryCount: request.maxRetryCount) - } - - private func publisher(request: NetworkingRequest, retryCount: Int) -> AnyPublisher { - guard let urlRequest = request.buildURLRequest() else { - return Fail(error: NetworkingError.unableToParseRequest as Error) - .eraseToAnyPublisher() - } - logger.log(request: urlRequest) - - let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) - return urlSession.dataTaskPublisher(for: urlRequest) - .tryMap { (data: Data, response: URLResponse) -> Data in - self.logger.log(response: response, data: data) - if let httpURLResponse = response as? HTTPURLResponse { - if !(200...299 ~= httpURLResponse.statusCode) { - var error = NetworkingError(errorCode: httpURLResponse.statusCode) - if let json = try? JSONSerialization.jsonObject(with: data, options: []) { - error.jsonPayload = JSON(jsonObject: json) - } - throw error - } - } - return data - } - // TODO fix retry -// .tryCatch({ [weak self, urlRequest] error -> AnyPublisher in -// guard -// let self = self, -// retryCount > 1, -// let retryPublisher = self.requestRetrier?(urlRequest, error) -// else { -// throw error -// } -// return retryPublisher -// .flatMap { _ -> AnyPublisher in -// self.publisher(request: request, retryCount: retryCount - 1) -// } -// .eraseToAnyPublisher() -// }) - .mapError { error -> NetworkingError in - return NetworkingError(error: error) - }.receive(on: DispatchQueue.main).eraseToAnyPublisher() - } - - func execute(request: NetworkingRequest) async throws -> Data { - guard let urlRequest = request.buildURLRequest() else { - throw NetworkingError.unableToParseRequest - } - logger.log(request: urlRequest) - let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) - let (data, response) = try await urlSession.data(for: urlRequest) - logger.log(response: response, data: data) - if let httpURLResponse = response as? HTTPURLResponse, !(200...299 ~= httpURLResponse.statusCode) { - var error = NetworkingError(errorCode: httpURLResponse.statusCode) - if let json = try? JSONSerialization.jsonObject(with: data, options: []) { - error.jsonPayload = JSON(jsonObject: json) - } - throw error - } - return data - } } -// Thansks to https://stackoverflow.com/questions/26364914/http-request-in-swift-with-post-method -extension CharacterSet { - static let urlQueryValueAllowed: CharacterSet = { - let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 - let subDelimitersToEncode = "!$&'()*+,;=" - var allowed = CharacterSet.urlQueryAllowed - allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") - return allowed - }() -} public enum ParameterEncoding { case urlEncoded case json } - extension NetworkingRequest { internal func buildURLRequest() -> URLRequest? { var urlString = url @@ -251,3 +122,15 @@ extension NetworkingRequest { + boundaryEnding } } + + +// Thansks to https://stackoverflow.com/questions/26364914/http-request-in-swift-with-post-method +extension CharacterSet { + static let urlQueryValueAllowed: CharacterSet = { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + var allowed = CharacterSet.urlQueryAllowed + allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + return allowed + }() +} diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index ecbd4a2..24ec30c 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -6,7 +6,6 @@ // import Foundation -import Combine public protocol NetworkingService { var network: NetworkingClient { get } @@ -14,221 +13,6 @@ public protocol NetworkingService { // Sugar, just forward calls to underlying network client -public extension NetworkingService { - - // Data - -// func get(_ route: String, params: Params = Params()) async -> AnyPublisher { -// network.get(route, params: params) -// } -// -// func post(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.post(route, params: params) -// } -// -// func post(_ route: String, body: Encodable) -> AnyPublisher { -// network.post(route, body: body) -// } -// -// func put(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.put(route, params: params) -// } -// -// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.patch(route, params: params) -// } -// -// func patch(_ route: String, body: Encodable) -> AnyPublisher { -// network.patch(route, body: body) -// } -// -// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.delete(route, params: params) -// } -// -// // Void -// -// func get(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.get(route, params: params) -// } -// -// func post(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.post(route, params: params) -// } -// -// func post(_ route: String, body: Encodable) -> AnyPublisher { -// network.post(route, body: body) -// } -// -// func put(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.put(route, params: params) -// } -// -// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.patch(route, params: params) -// } -// -// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.delete(route, params: params) -// } -// -// // JSON -// -// func get(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.get(route, params: params) -// } -// -// func post(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.post(route, params: params) -// } -// -// func post(_ route: String, body: Encodable) -> AnyPublisher { -// network.post(route, body: body) -// } -// -// func put(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.put(route, params: params) -// } -// -// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.patch(route, params: params) -// } -// -// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.delete(route, params: params) -// } -// -// // Decodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.delete(route, params: params, keypath: keypath) -// } -// -// // Array Decodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.delete(route, params: params, keypath: keypath) -// } -// -// // NetworkingJSONDecodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.delete(route, params: params, keypath: keypath) -// } -// -// -// -// // Array NetworkingJSONDecodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.delete(route, params: params, keypath: keypath) -// } -} - // Async public extension NetworkingService { @@ -399,4 +183,3 @@ public extension NetworkingService { try await network.delete(route, params: params, keypath: keypath) } } - From 2295056e70f10edb8d78762aabfd72f4a1de9cb5 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Thu, 26 Sep 2024 17:07:17 -1000 Subject: [PATCH 08/25] WIP separate Combine tests from others --- Sources/Networking/Calls/JSON.swift | 2 +- .../Calls/NetworkingClient+JSON.swift | 14 + .../Combine/NetworkingRequest+Publisher.swift | 2 +- .../Combine/NetworkingService+Combine.swift | 420 +++++++-------- Sources/Networking/NetworkingClient.swift | 2 +- Sources/Networking/NetworkingService.swift | 8 + .../Combine/DeleteRequestTests+Combine.swift | 237 +++++++++ .../Combine/GetRequestTests+Combine.swift | 242 +++++++++ .../Combine/PatchRequestTests+Combine.swift | 235 +++++++++ .../Combine/PostRequestTests+Combine.swift | 273 ++++++++++ .../NetworkingTests/DeleteRequestTests.swift | 410 ++++----------- Tests/NetworkingTests/GetRequestTests.swift | 239 +-------- .../NetworkingTests/MockingURLProtocol.swift | 96 ++-- Tests/NetworkingTests/PatchRequestTests.swift | 409 ++++----------- Tests/NetworkingTests/PostRequestTests.swift | 485 +++++------------- Tests/NetworkingTests/PutRequestTests.swift | 208 ++++---- 16 files changed, 1694 insertions(+), 1588 deletions(-) create mode 100644 Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift create mode 100644 Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift create mode 100644 Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift create mode 100644 Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift diff --git a/Sources/Networking/Calls/JSON.swift b/Sources/Networking/Calls/JSON.swift index 5f45ead..951ae32 100644 --- a/Sources/Networking/Calls/JSON.swift +++ b/Sources/Networking/Calls/JSON.swift @@ -25,7 +25,7 @@ public struct JSON: Sendable, CustomStringConvertible { } } - var value: Any { + public var value: Any { return array ?? dictionary ?? "" } diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift index 242e743..d6d729b 100644 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ b/Sources/Networking/Calls/NetworkingClient+JSON.swift @@ -8,6 +8,13 @@ import Foundation public extension NetworkingClient { + + func get(_ route: String, params: Params = Params()) async throws -> Any { + let req = request(.get, route, params: params) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return json + } func get(_ route: String, params: Params = Params()) async throws -> JSON { let req = request(.get, route, params: params) @@ -37,6 +44,13 @@ public extension NetworkingClient { return JSON(jsonObject: json) } + func patch(_ route: String, params: Params = Params()) async throws -> Any { + let req = request(.patch, route, params: params) + let data = try await execute(request: req) + let json = try JSONSerialization.jsonObject(with: data, options: []) + return json + } + func patch(_ route: String, params: Params = Params()) async throws -> JSON { let req = request(.patch, route, params: params) let data = try await execute(request: req) diff --git a/Sources/Networking/Combine/NetworkingRequest+Publisher.swift b/Sources/Networking/Combine/NetworkingRequest+Publisher.swift index fe742a6..46255f4 100644 --- a/Sources/Networking/Combine/NetworkingRequest+Publisher.swift +++ b/Sources/Networking/Combine/NetworkingRequest+Publisher.swift @@ -6,7 +6,7 @@ // import Foundation -@preconcurrency import Combine +import Combine extension NetworkingClient { diff --git a/Sources/Networking/Combine/NetworkingService+Combine.swift b/Sources/Networking/Combine/NetworkingService+Combine.swift index cd6f6e1..0894f07 100644 --- a/Sources/Networking/Combine/NetworkingService+Combine.swift +++ b/Sources/Networking/Combine/NetworkingService+Combine.swift @@ -6,7 +6,7 @@ // import Foundation -@preconcurrency import Combine +import Combine // Sugar, just forward calls to underlying network client @@ -14,214 +14,214 @@ public extension NetworkingService { // Data -// func get(_ route: String, params: Params = Params()) async -> AnyPublisher { -// await network.get(route, params: params) -// } -// -// func post(_ route: String, params: Params = Params()) async -> AnyPublisher { -// await network.post(route, params: params) -// } -// -// func post(_ route: String, body: Encodable & Sendable) async -> AnyPublisher { -// await network.post(route, body: body) -// } -// -// func put(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.put(route, params: params) -// } -// -// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.patch(route, params: params) -// } -// -// func patch(_ route: String, body: Encodable) -> AnyPublisher { -// network.patch(route, body: body) -// } -// -// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.delete(route, params: params) -// } -// -// // Void -// -// func get(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.get(route, params: params) -// } -// -// func post(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.post(route, params: params) -// } -// -// func post(_ route: String, body: Encodable) -> AnyPublisher { -// network.post(route, body: body) -// } -// -// func put(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.put(route, params: params) -// } -// -// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.patch(route, params: params) -// } -// -// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.delete(route, params: params) -// } -// -// // JSON -// -// func get(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.get(route, params: params) -// } -// -// func post(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.post(route, params: params) -// } -// -// func post(_ route: String, body: Encodable) -> AnyPublisher { -// network.post(route, body: body) -// } -// -// func put(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.put(route, params: params) -// } -// -// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.patch(route, params: params) -// } -// -// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { -// network.delete(route, params: params) -// } -// -// // Decodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.delete(route, params: params, keypath: keypath) -// } -// -// // Array Decodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher where T: Collection { -// network.delete(route, params: params, keypath: keypath) -// } -// -// // NetworkingJSONDecodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher { -// network.delete(route, params: params, keypath: keypath) -// } -// -// -// -// // Array NetworkingJSONDecodable -// -// func get(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.get(route, params: params, keypath: keypath) -// } -// -// func post(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.post(route, params: params, keypath: keypath) -// } -// -// func put(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.put(route, params: params, keypath: keypath) -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.patch(route, params: params, keypath: keypath) -// } -// -// func delete(_ route: String, -// params: Params = Params(), -// keypath: String? = nil) -> AnyPublisher<[T], Error> { -// network.delete(route, params: params, keypath: keypath) -// } + func get(_ route: String, params: Params = Params()) -> AnyPublisher { + network.get(route, params: params) + } + + func post(_ route: String, params: Params = Params()) -> AnyPublisher { + network.post(route, params: params) + } + + func post(_ route: String, body: Encodable & Sendable) -> AnyPublisher { + network.post(route, body: body) + } + + func put(_ route: String, params: Params = Params()) -> AnyPublisher { + network.put(route, params: params) + } + + func patch(_ route: String, params: Params = Params()) -> AnyPublisher { + network.patch(route, params: params) + } + + func patch(_ route: String, body: Encodable) -> AnyPublisher { + network.patch(route, body: body) + } + + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + network.delete(route, params: params) + } + + // Void + + func get(_ route: String, params: Params = Params()) -> AnyPublisher { + network.get(route, params: params) + } + + func post(_ route: String, params: Params = Params()) -> AnyPublisher { + network.post(route, params: params) + } + + func post(_ route: String, body: Encodable) -> AnyPublisher { + network.post(route, body: body) + } + + func put(_ route: String, params: Params = Params()) -> AnyPublisher { + network.put(route, params: params) + } + + func patch(_ route: String, params: Params = Params()) -> AnyPublisher { + network.patch(route, params: params) + } + + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + network.delete(route, params: params) + } + + // JSON + + func get(_ route: String, params: Params = Params()) -> AnyPublisher { + network.get(route, params: params) + } + + func post(_ route: String, params: Params = Params()) -> AnyPublisher { + network.post(route, params: params) + } + + func post(_ route: String, body: Encodable) -> AnyPublisher { + network.post(route, body: body) + } + + func put(_ route: String, params: Params = Params()) -> AnyPublisher { + network.put(route, params: params) + } + + func patch(_ route: String, params: Params = Params()) -> AnyPublisher { + network.patch(route, params: params) + } + + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + network.delete(route, params: params) + } + + // Decodable + + func get(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.get(route, params: params, keypath: keypath) + } + + func post(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.post(route, params: params, keypath: keypath) + } + + func put(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.put(route, params: params, keypath: keypath) + } + + func patch(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.patch(route, params: params, keypath: keypath) + } + + func delete(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.delete(route, params: params, keypath: keypath) + } + + // Array Decodable + + func get(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.get(route, params: params, keypath: keypath) + } + + func post(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.post(route, params: params, keypath: keypath) + } + + func put(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.put(route, params: params, keypath: keypath) + } + + func patch(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.patch(route, params: params, keypath: keypath) + } + + func delete(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.delete(route, params: params, keypath: keypath) + } + + // NetworkingJSONDecodable + + func get(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.get(route, params: params, keypath: keypath) + } + + func post(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.post(route, params: params, keypath: keypath) + } + + func put(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.put(route, params: params, keypath: keypath) + } + + func patch(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.patch(route, params: params, keypath: keypath) + } + + func delete(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher { + network.delete(route, params: params, keypath: keypath) + } + + + + // Array NetworkingJSONDecodable + + func get(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.get(route, params: params, keypath: keypath) + } + + func post(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.post(route, params: params, keypath: keypath) + } + + func put(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.put(route, params: params, keypath: keypath) + } + + func patch(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.patch(route, params: params, keypath: keypath) + } + + func delete(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.delete(route, params: params, keypath: keypath) + } } diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index 4e1278a..d8c6797 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -18,7 +18,7 @@ actor NetworkingClientURLSessionDelegate: NSObject, URLSessionDelegate { // public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? -public actor NetworkingClient { +public class NetworkingClient { /** Instead of using the same keypath for every call eg: "collection", this enables to use a default keypath for parsing collections. diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index 24ec30c..740220c 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -69,6 +69,10 @@ public extension NetworkingService { } // JSON + + func get(_ route: String, params: Params = Params()) async throws -> Any { + try await network.get(route, params: params) + } func get(_ route: String, params: Params = Params()) async throws -> JSON { try await network.get(route, params: params) @@ -85,6 +89,10 @@ public extension NetworkingService { func put(_ route: String, params: Params = Params()) async throws -> JSON { try await network.put(route, params: params) } + + func patch(_ route: String, params: Params = Params()) async throws -> Any { + try await network.patch(route, params: params) + } func patch(_ route: String, params: Params = Params()) async throws -> JSON { try await network.patch(route, params: params) diff --git a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift new file mode 100644 index 0000000..ff52d5b --- /dev/null +++ b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift @@ -0,0 +1,237 @@ +// +// DeleteRequestTests.swift +// +// +// Created by Sacha DSO on 12/04/2022. +// + +import Testing +import Foundation +import Networking + +@Suite +struct DeleteRequestTestsCombine { + +// private let network = NetworkingClient(baseURL: "https://mocked.com") +//// private var cancellables = Set() +// +// init() { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } + +// func testDELETEVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.delete("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// func testDELETEJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// func testDELETENetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + +// func testDELETEDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testDELETEArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + +// func testDELETEArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.delete("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + +} diff --git a/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift new file mode 100644 index 0000000..9629da6 --- /dev/null +++ b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift @@ -0,0 +1,242 @@ +// +// GetRequestTests.swift +// +// +// Created by Sacha DSO on 12/04/2022. +// + +import Testing +import Foundation +import Combine + +@testable +import Networking + +@Suite(.serialized) +struct GetRequestCombineTests { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + private var cancellables = Set() + + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +// func testGETVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.get("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// +// func testGETDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// func testGETJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testGETNetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + +// func testGETDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// func testGETArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// +// +// func testGETArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.get("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +} + diff --git a/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift new file mode 100644 index 0000000..d49cd7e --- /dev/null +++ b/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift @@ -0,0 +1,235 @@ +//// +//// PatchRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// +// +//import Foundation +//import XCTest +//import Combine +// +//@testable +//import Networking +// +//class PatchRequestTests: XCTestCase { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// override func setUpWithError() throws { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +//// func testPATCHVoidWorks() { +//// MockingURLProtocol.mockedResponse = +//// """ +//// { "response": "OK" } +//// """ +//// let expectationWorks = expectation(description: "Call works") +//// let expectationFinished = expectation(description: "Finished") +//// network.patch("/users").sink { completion in +//// switch completion { +//// case .failure(_): +//// XCTFail() +//// case .finished: +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +//// expectationFinished.fulfill() +//// } +//// } receiveValue: { () in +//// expectationWorks.fulfill() +//// } +//// .store(in: &cancellables) +//// waitForExpectations(timeout: 0.1) +//// } +//// func testPATCHDataWorks() { +//// MockingURLProtocol.mockedResponse = +//// """ +//// { "response": "OK" } +//// """ +//// let expectationWorks = expectation(description: "ReceiveValue called") +//// let expectationFinished = expectation(description: "Finished called") +//// network.patch("/users").sink { completion in +//// switch completion { +//// case .failure: +//// XCTFail() +//// case .finished: +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +//// expectationFinished.fulfill() +//// } +//// } receiveValue: { (data: Data) in +//// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +//// expectationWorks.fulfill() +//// } +//// .store(in: &cancellables) +//// waitForExpectations(timeout: 0.1) +//// } +//// func testPATCHJSONWorks() { +//// MockingURLProtocol.mockedResponse = +//// """ +//// {"response":"OK"} +//// """ +//// let expectationWorks = expectation(description: "ReceiveValue called") +//// let expectationFinished = expectation(description: "Finished called") +//// network.patch("/users").sink { completion in +//// switch completion { +//// case .failure: +//// XCTFail() +//// case .finished: +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +//// expectationFinished.fulfill() +//// } +//// } receiveValue: { (json: Any) in +//// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +//// let expectedResponseData = +//// """ +//// {"response":"OK"} +//// """.data(using: String.Encoding.utf8) +//// +//// XCTAssertEqual(data, expectedResponseData) +//// expectationWorks.fulfill() +//// } +//// .store(in: &cancellables) +//// waitForExpectations(timeout: 0.1) +//// } +//// func testPATCHNetworkingJSONDecodableWorks() { +//// MockingURLProtocol.mockedResponse = +//// """ +//// { +//// "title":"Hello", +//// "content":"World", +//// } +//// """ +//// let expectationWorks = expectation(description: "ReceiveValue called") +//// let expectationFinished = expectation(description: "Finished called") +//// network.patch("/posts/1") +//// .sink { completion in +//// switch completion { +//// case .failure: +//// XCTFail() +//// case .finished: +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +//// expectationFinished.fulfill() +//// } +//// } receiveValue: { (post: Post) in +//// XCTAssertEqual(post.title, "Hello") +//// XCTAssertEqual(post.content, "World") +//// expectationWorks.fulfill() +//// } +//// .store(in: &cancellables) +//// waitForExpectations(timeout: 0.1) +//// } +//// func testPATCHDecodableWorks() { +//// MockingURLProtocol.mockedResponse = +//// """ +//// { +//// "firstname":"John", +//// "lastname":"Doe", +//// } +//// """ +//// let expectationWorks = expectation(description: "ReceiveValue called") +//// let expectationFinished = expectation(description: "Finished called") +//// network.patch("/users/1") +//// .sink { completion in +//// switch completion { +//// case .failure: +//// XCTFail() +//// case .finished: +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +//// expectationFinished.fulfill() +//// } +//// } receiveValue: { (userJSON: UserJSON) in +//// XCTAssertEqual(userJSON.firstname, "John") +//// XCTAssertEqual(userJSON.lastname, "Doe") +//// expectationWorks.fulfill() +//// } +//// .store(in: &cancellables) +//// waitForExpectations(timeout: 0.1) +//// } +//// func testPATCHArrayOfDecodableWorks() { +//// MockingURLProtocol.mockedResponse = +//// """ +//// [ +//// { +//// "firstname":"John", +//// "lastname":"Doe" +//// }, +//// { +//// "firstname":"Jimmy", +//// "lastname":"Punchline" +//// } +//// ] +//// """ +//// let expectationWorks = expectation(description: "ReceiveValue called") +//// let expectationFinished = expectation(description: "Finished called") +//// network.patch("/users") +//// .sink { completion in +//// switch completion { +//// case .failure: +//// XCTFail() +//// case .finished: +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +//// expectationFinished.fulfill() +//// } +//// } receiveValue: { (userJSON: [UserJSON]) in +//// XCTAssertEqual(userJSON[0].firstname, "John") +//// XCTAssertEqual(userJSON[0].lastname, "Doe") +//// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +//// XCTAssertEqual(userJSON[1].lastname, "Punchline") +//// expectationWorks.fulfill() +//// } +//// .store(in: &cancellables) +//// waitForExpectations(timeout: 0.1) +//// } +//// func testPATCHArrayOfDecodableWithKeypathWorks() { +//// MockingURLProtocol.mockedResponse = +//// """ +//// { +//// "users" : +//// [ +//// { +//// "firstname":"John", +//// "lastname":"Doe" +//// }, +//// { +//// "firstname":"Jimmy", +//// "lastname":"Punchline" +//// } +//// ] +//// } +//// """ +//// let expectationWorks = expectation(description: "ReceiveValue called") +//// let expectationFinished = expectation(description: "Finished called") +//// network.patch("/users", keypath: "users") +//// .sink { completion in +//// switch completion { +//// case .failure: +//// XCTFail() +//// case .finished: +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") +//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +//// expectationFinished.fulfill() +//// } +//// } receiveValue: { (userJSON: [UserJSON]) in +//// XCTAssertEqual(userJSON[0].firstname, "John") +//// XCTAssertEqual(userJSON[0].lastname, "Doe") +//// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +//// XCTAssertEqual(userJSON[1].lastname, "Punchline") +//// expectationWorks.fulfill() +//// } +//// .store(in: &cancellables) +//// waitForExpectations(timeout: 0.1) +//// } +// +//} diff --git a/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift new file mode 100644 index 0000000..a3e05c5 --- /dev/null +++ b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift @@ -0,0 +1,273 @@ +//// +//// PostRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// +// +//import Foundation +//import XCTest +//import Combine +// +//@testable +//import Networking +// +// +//class PostRequestTests: XCTestCase { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// override func setUpWithError() throws { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// override func tearDownWithError() throws { +// MockingURLProtocol.mockedResponse = "" +// MockingURLProtocol.currentRequest = nil +// } +// +// func testPOSTVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.post("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTNetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.post("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPOSTDataEncodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// +// let creds = Credentials(username: "Alan", password: "Turing") +// network.post("/users", body: creds).sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// +// let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() +// XCTAssertEqual(body?["username"] as? String, "Alan") +// XCTAssertEqual(body?["password"] as? String, "Turing") +// +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +//} diff --git a/Tests/NetworkingTests/DeleteRequestTests.swift b/Tests/NetworkingTests/DeleteRequestTests.swift index 19ee5c0..aabff7b 100644 --- a/Tests/NetworkingTests/DeleteRequestTests.swift +++ b/Tests/NetworkingTests/DeleteRequestTests.swift @@ -1,316 +1,100 @@ -//// -//// DeleteRequestTests.swift -//// -//// -//// Created by Sacha DSO on 12/04/2022. -//// // -//import Foundation -//import XCTest -//import Combine +// DeleteRequestTests.swift +// // -//@testable -//import Networking +// Created by Sacha DSO on 12/04/2022. // -//class DeletehRequestTests: XCTestCase { -// -// private let network = NetworkingClient(baseURL: "https://mocked.com") -// private var cancellables = Set() -// -// override func setUpWithError() throws { -// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] -// } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } -// -// func testDELETEVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.delete("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEVoidAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let _: Void = try await network.delete("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// } -// -// func testDELETEDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEDataAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let data: Data = try await network.delete("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// } -// -// func testDELETEJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEJSONAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let json: Any = try await network.delete("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// XCTAssertEqual(data, expectedResponseData) -// } -// -// func testDELETENetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let userJSON: UserJSON = try await network.delete("/users/1") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// } -// -// func testDELETEArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEArrayOfDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let users: [UserJSON] = try await network.delete("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(users[0].firstname, "John") -// XCTAssertEqual(users[0].lastname, "Doe") -// XCTAssertEqual(users[1].firstname, "Jimmy") -// XCTAssertEqual(users[1].lastname, "Punchline") -// } -// -// func testDELETEArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -//} + +import Testing +import Foundation +import Networking + +@Suite(.serialized) +struct DeleteRequestTests { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + @Test + func DELETEVoidAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _: Void = try await network.delete("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + + @Test + func DELETEDataAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let data: Data = try await network.delete("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } + + @Test + func DELETEJSONAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let json: JSON = try await network.delete("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + #expect(data == expectedResponseData) + } + + @Test + func DELETEDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let userJSON: UserJSON = try await network.delete("/users/1") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") + } + + @Test + func DELETEArrayOfDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let users: [UserJSON] = try await network.delete("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(users[0].firstname == "John") + #expect(users[0].lastname == "Doe") + #expect(users[1].firstname == "Jimmy") + #expect(users[1].lastname == "Punchline") + } +} diff --git a/Tests/NetworkingTests/GetRequestTests.swift b/Tests/NetworkingTests/GetRequestTests.swift index f6235ca..78d9201 100644 --- a/Tests/NetworkingTests/GetRequestTests.swift +++ b/Tests/NetworkingTests/GetRequestTests.swift @@ -7,47 +7,17 @@ import Testing import Foundation -import Combine - -@testable import Networking @Suite(.serialized) struct GetRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - init() async { - await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } -// -// func testGETVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.get("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// + @Test func GETVoidAsyncWorks() async throws { MockingURLProtocol.mockedResponse = @@ -70,32 +40,7 @@ struct GetRequestTests { #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users?search=lion") } -// -// func testGETDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - + @Test func GETDataAsyncWorks() async throws { MockingURLProtocol.mockedResponse = @@ -108,112 +53,22 @@ struct GetRequestTests { #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) } -// func testGETJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// @Test func GETJSONAsyncWorks() async throws { - MockingURLProtocol.mockedResponse = + let mockedJson = """ - { "response": "OK" } + {"response":"OK"} """ + MockingURLProtocol.mockedResponse = mockedJson let json: JSON = try await network.get("/users") #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) + let expectedResponseData = mockedJson.data(using: String.Encoding.utf8) let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) #expect(data == expectedResponseData) } -// func testGETNetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - -// func testGETDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -//// @Test func GETNetworkingJSONDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = @@ -230,43 +85,6 @@ struct GetRequestTests { #expect(userJSON.lastname == "Doe") } -// func testGETArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// @Test func GETArrayOfDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = @@ -290,46 +108,5 @@ struct GetRequestTests { #expect(users[1].firstname == "Jimmy") #expect(users[1].lastname == "Punchline") } -// -// -// func testGETArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } } diff --git a/Tests/NetworkingTests/MockingURLProtocol.swift b/Tests/NetworkingTests/MockingURLProtocol.swift index 03aab09..e3f3ddc 100644 --- a/Tests/NetworkingTests/MockingURLProtocol.swift +++ b/Tests/NetworkingTests/MockingURLProtocol.swift @@ -32,51 +32,51 @@ class MockingURLProtocol: URLProtocol { override func stopLoading() { } } -// -// -//extension Data { -// init(inputStream: InputStream) throws { -// inputStream.open() -// defer { inputStream.close() } -// self.init() -// let bufferSize = 512 -// var readBuffer = [UInt8](repeating: 0, count: bufferSize) -// while inputStream.hasBytesAvailable { -// let readBytes = inputStream.read(&readBuffer, maxLength: bufferSize) -// switch readBytes { -// case 0: -// break -// case ..<0: -// throw inputStream.streamError! -// default: -// append(readBuffer, count: readBytes) -// } -// } -// } -//} -// -// -//extension URLRequest { -// func httpBodyStreamAsData() -> Data? { -// guard let httpBodyStream else { return nil } -// do { -// return try Data(inputStream: httpBodyStream) -// } catch { -// return nil -// } -// } -// -// func httpBodyStreamAsJSON() -> Any? { -// guard let httpBodyStreamAsData = httpBodyStreamAsData() else { return nil } -// do { -// let json = try JSONSerialization.jsonObject(with: httpBodyStreamAsData) -// return json -// } catch { -// return nil -// } -// } -// -// func httpBodyStreamAsDictionary() -> [String: Any] { -// return httpBodyStreamAsJSON() as? [String: Any] ?? [:] -// } -//} + + +extension Data { + init(inputStream: InputStream) throws { + inputStream.open() + defer { inputStream.close() } + self.init() + let bufferSize = 512 + var readBuffer = [UInt8](repeating: 0, count: bufferSize) + while inputStream.hasBytesAvailable { + let readBytes = inputStream.read(&readBuffer, maxLength: bufferSize) + switch readBytes { + case 0: + break + case ..<0: + throw inputStream.streamError! + default: + append(readBuffer, count: readBytes) + } + } + } +} + + +extension URLRequest { + func httpBodyStreamAsData() -> Data? { + guard let httpBodyStream else { return nil } + do { + return try Data(inputStream: httpBodyStream) + } catch { + return nil + } + } + + func httpBodyStreamAsJSON() -> Any? { + guard let httpBodyStreamAsData = httpBodyStreamAsData() else { return nil } + do { + let json = try JSONSerialization.jsonObject(with: httpBodyStreamAsData) + return json + } catch { + return nil + } + } + + func httpBodyStreamAsDictionary() -> [String: Any] { + return httpBodyStreamAsJSON() as? [String: Any] ?? [:] + } +} diff --git a/Tests/NetworkingTests/PatchRequestTests.swift b/Tests/NetworkingTests/PatchRequestTests.swift index 09e3f07..aea948f 100644 --- a/Tests/NetworkingTests/PatchRequestTests.swift +++ b/Tests/NetworkingTests/PatchRequestTests.swift @@ -1,316 +1,99 @@ -//// -//// PatchRequestTests.swift -//// -//// -//// Created by Sacha DSO on 12/04/2022. -//// // -//import Foundation -//import XCTest -//import Combine +// PatchRequestTests.swift +// // -//@testable -//import Networking +// Created by Sacha DSO on 12/04/2022. // -//class PatchRequestTests: XCTestCase { -// -// private let network = NetworkingClient(baseURL: "https://mocked.com") -// private var cancellables = Set() -// -// override func setUpWithError() throws { -// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] -// } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } -// -// func testPATCHVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.patch("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPATCHVoidAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let _:Void = try await network.patch("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// } -// -// func testPATCHDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.patch("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPATCHDataAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let data: Data = try await network.patch("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// } -// -// func testPATCHJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.patch("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPATCHJSONAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let json: Any = try await network.patch("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// XCTAssertEqual(data, expectedResponseData) -// } -// -// func testPATCHNetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.patch("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPATCHDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.patch("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPATCHDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let user: UserJSON = try await network.patch("/users/1") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// XCTAssertEqual(user.firstname, "John") -// XCTAssertEqual(user.lastname, "Doe") -// } -// -// func testPATCHArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.patch("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPATCHArrayOfDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let users: [UserJSON] = try await network.patch("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(users[0].firstname, "John") -// XCTAssertEqual(users[0].lastname, "Doe") -// XCTAssertEqual(users[1].firstname, "Jimmy") -// XCTAssertEqual(users[1].lastname, "Punchline") -// } -// -// func testPATCHArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.patch("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -//} + +import Foundation +import XCTest +import Networking + +class PatchRequestTests: XCTestCase { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + + override func setUpWithError() throws { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + override func tearDownWithError() throws { + MockingURLProtocol.mockedResponse = "" + MockingURLProtocol.currentRequest = nil + } + + func testPATCHVoidAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _:Void = try await network.patch("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + } + + func testPATCHDataAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let data: Data = try await network.patch("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } + + func testPATCHJSONAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let json: Any = try await network.patch("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + XCTAssertEqual(data, expectedResponseData) + } + + func testPATCHDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let user: UserJSON = try await network.patch("/users/1") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") + XCTAssertEqual(user.firstname, "John") + XCTAssertEqual(user.lastname, "Doe") + } + + func testPATCHArrayOfDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let users: [UserJSON] = try await network.patch("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + XCTAssertEqual(users[0].firstname, "John") + XCTAssertEqual(users[0].lastname, "Doe") + XCTAssertEqual(users[1].firstname, "Jimmy") + XCTAssertEqual(users[1].lastname, "Punchline") + } +} diff --git a/Tests/NetworkingTests/PostRequestTests.swift b/Tests/NetworkingTests/PostRequestTests.swift index 4859f04..ab7832d 100644 --- a/Tests/NetworkingTests/PostRequestTests.swift +++ b/Tests/NetworkingTests/PostRequestTests.swift @@ -1,369 +1,122 @@ -//// -//// PostRequestTests.swift -//// -//// -//// Created by Sacha DSO on 12/04/2022. -//// // -//import Foundation -//import XCTest -//import Combine +// PostRequestTests.swift +// // -//@testable -//import Networking +// Created by Sacha DSO on 12/04/2022. // -//class PostRequestTests: XCTestCase { -// -// private let network = NetworkingClient(baseURL: "https://mocked.com") -// private var cancellables = Set() -// -// override func setUpWithError() throws { -// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] -// } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } -// -// func testPOSTVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.post("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTVoidAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let _: Void = try await network.post("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// } -// -// func testPOSTDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTDataAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let data: Data = try await network.post("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// } -// -// func testPOSTJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTJSONAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let json: Any = try await network.post("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// XCTAssertEqual(data, expectedResponseData) -// } -// -// func testPOSTNetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let userJSON:UserJSON = try await network.post("/users/1") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// } -// -// func testPOSTArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTArrayOfDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let users: [UserJSON] = try await network.post("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(users[0].firstname, "John") -// XCTAssertEqual(users[0].lastname, "Doe") -// XCTAssertEqual(users[1].firstname, "Jimmy") -// XCTAssertEqual(users[1].lastname, "Punchline") -// } -// -// func testPOSTArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testAsyncPostEncodable() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// -// let creds = Credentials(username: "john", password: "doe") -// let data: Data = try await network.post("/users", body: creds) -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// -// let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() -// XCTAssertEqual(body?["username"] as? String, "john") -// XCTAssertEqual(body?["password"] as? String, "doe") -// } -// -// func testPOSTDataEncodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// -// let creds = Credentials(username: "Alan", password: "Turing") -// network.post("/users", body: creds).sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// -// let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() -// XCTAssertEqual(body?["username"] as? String, "Alan") -// XCTAssertEqual(body?["password"] as? String, "Turing") -// -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -//} -// -//struct Credentials: Encodable { -// let username: String -// let password: String -//} + +import Foundation +import XCTest +import Networking + + +class PostRequestTests: XCTestCase { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + + override func setUpWithError() throws { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + override func tearDownWithError() throws { + MockingURLProtocol.mockedResponse = "" + MockingURLProtocol.currentRequest = nil + } + + func testPOSTVoidAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _: Void = try await network.post("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + } + + func testPOSTDataAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let data: Data = try await network.post("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } + + func testPOSTJSONAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let json: JSON = try await network.post("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + XCTAssertEqual(data, expectedResponseData) + } + + func testPOSTDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let userJSON:UserJSON = try await network.post("/users/1") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") + XCTAssertEqual(userJSON.firstname, "John") + XCTAssertEqual(userJSON.lastname, "Doe") + } + + func testPOSTArrayOfDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let users: [UserJSON] = try await network.post("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + XCTAssertEqual(users[0].firstname, "John") + XCTAssertEqual(users[0].lastname, "Doe") + XCTAssertEqual(users[1].firstname, "Jimmy") + XCTAssertEqual(users[1].lastname, "Punchline") + } + + func testAsyncPostEncodable() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + + let creds = Credentials(username: "john", password: "doe") + let data: Data = try await network.post("/users", body: creds) + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + + let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() + XCTAssertEqual(body?["username"] as? String, "john") + XCTAssertEqual(body?["password"] as? String, "doe") + } +} + +struct Credentials: Encodable { + let username: String + let password: String +} diff --git a/Tests/NetworkingTests/PutRequestTests.swift b/Tests/NetworkingTests/PutRequestTests.swift index 41797dc..43b5f2a 100644 --- a/Tests/NetworkingTests/PutRequestTests.swift +++ b/Tests/NetworkingTests/PutRequestTests.swift @@ -1,31 +1,31 @@ -//// -//// PutRequestTests.swift -//// -//// -//// Created by Sacha DSO on 12/04/2022. -//// // -//import Foundation -//import XCTest -//import Combine +// PutRequestTests.swift // -//@testable -//import Networking // -//class PutRequestTests: XCTestCase { -// -// private let network = NetworkingClient(baseURL: "https://mocked.com") -// private var cancellables = Set() -// -// override func setUpWithError() throws { -// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] -// } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } +// Created by Sacha DSO on 12/04/2022. // + +import Foundation +import XCTest +import Combine + +@testable +import Networking + +class PutRequestTests: XCTestCase { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + private var cancellables = Set() + + override func setUpWithError() throws { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + override func tearDownWithError() throws { + MockingURLProtocol.mockedResponse = "" + MockingURLProtocol.currentRequest = nil + } + // func testPUTVoidWorks() { // MockingURLProtocol.mockedResponse = // """ @@ -48,17 +48,17 @@ // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -// func testPUTVoidAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let _: Void = try await network.put("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// } -// + + func testPUTVoidAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _: Void = try await network.put("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + } + // func testPUTDataWorks() { // MockingURLProtocol.mockedResponse = // """ @@ -82,17 +82,17 @@ // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -// func testPUTDataAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let data: Data = try await network.put("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// } + + func testPUTDataAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let data: Data = try await network.put("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } // // func testPUTJSONWorks() { // MockingURLProtocol.mockedResponse = @@ -123,22 +123,22 @@ // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -// func testPUTJSONAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let json: Any = try await network.put("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// XCTAssertEqual(data, expectedResponseData) -// } + + func testPUTJSONAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let json: Any = try await network.put("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + XCTAssertEqual(data, expectedResponseData) + } // // func testPUTNetworkingJSONDecodableWorks() { // MockingURLProtocol.mockedResponse = @@ -197,22 +197,22 @@ // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -// func testPUTDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let user: UserJSON = try await network.put("/users/1") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// XCTAssertEqual(user.firstname, "John") -// XCTAssertEqual(user.lastname, "Doe") -// } -// + + func testPUTDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let user: UserJSON = try await network.put("/users/1") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") + XCTAssertEqual(user.firstname, "John") + XCTAssertEqual(user.lastname, "Doe") + } + // func testPUTArrayOfDecodableWorks() { // MockingURLProtocol.mockedResponse = // """ @@ -249,30 +249,30 @@ // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -// func testPUTArrayOfDecodableAsyncWorks() async throws { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let users: [UserJSON] = try await network.put("/users") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// XCTAssertEqual(users[0].firstname, "John") -// XCTAssertEqual(users[0].lastname, "Doe") -// XCTAssertEqual(users[1].firstname, "Jimmy") -// XCTAssertEqual(users[1].lastname, "Punchline") -// } -// + + func testPUTArrayOfDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let users: [UserJSON] = try await network.put("/users") + XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") + XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + XCTAssertEqual(users[0].firstname, "John") + XCTAssertEqual(users[0].lastname, "Doe") + XCTAssertEqual(users[1].firstname, "Jimmy") + XCTAssertEqual(users[1].lastname, "Punchline") + } + // func testPUTArrayOfDecodableWithKeypathWorks() { // MockingURLProtocol.mockedResponse = // """ @@ -312,5 +312,5 @@ // .store(in: &cancellables) // waitForExpectations(timeout: 0.1) // } -// -//} + +} From 6ff78a2565570fea9f0a9f43313c595e90da2018 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 2 Oct 2024 06:39:40 -1000 Subject: [PATCH 09/25] Adds xctestplan with "parallelizable: false" --- .swiftpm/Networking.xctestplan | 25 ++++++ .../xcschemes/Networking.xcscheme | 84 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 .swiftpm/Networking.xctestplan create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme diff --git a/.swiftpm/Networking.xctestplan b/.swiftpm/Networking.xctestplan new file mode 100644 index 0000000..0f32567 --- /dev/null +++ b/.swiftpm/Networking.xctestplan @@ -0,0 +1,25 @@ +{ + "configurations" : [ + { + "id" : "1F7E75BB-2848-40A3-B102-03A60E30171E", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + + }, + "testTargets" : [ + { + "parallelizable" : false, + "target" : { + "containerPath" : "container:", + "identifier" : "NetworkingTests", + "name" : "NetworkingTests" + } + } + ], + "version" : 1 +} diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme new file mode 100644 index 0000000..be718a8 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cb9a42de76b7aa1a15c0cc99633cf813dc686dff Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 2 Oct 2024 07:20:28 -1000 Subject: [PATCH 10/25] Migrates more tests to Swift Testing --- .../Combine/DeleteRequestTests+Combine.swift | 2 +- .../Combine/PutRequestTests+Combine.swift | 318 ++++++++++++++++++ Tests/NetworkingTests/NetworkingTests.swift | 51 ++- Tests/NetworkingTests/PatchRequestTests.swift | 64 ++-- Tests/NetworkingTests/PostRequestTests.swift | 65 ++-- Tests/NetworkingTests/PutRequestTests.swift | 302 +++-------------- 6 files changed, 461 insertions(+), 341 deletions(-) create mode 100644 Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift diff --git a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift index ff52d5b..32cbe4a 100644 --- a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift @@ -10,7 +10,7 @@ import Foundation import Networking @Suite -struct DeleteRequestTestsCombine { +struct DeleteRequestCombineTests { // private let network = NetworkingClient(baseURL: "https://mocked.com") //// private var cancellables = Set() diff --git a/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift new file mode 100644 index 0000000..03c04b8 --- /dev/null +++ b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift @@ -0,0 +1,318 @@ +// +// PutRequestTests.swift +// +// +// Created by Sacha DSO on 12/04/2022. +// + +import Foundation +import Testing +import Combine + +@testable +import Networking + +@Suite +struct PutRequestCombineTests { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + private var cancellables = Set() + + init() { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + +// func testPUTVoidWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "Call works") +// let expectationFinished = expectation(description: "Finished") +// network.put("/users").sink { completion in +// switch completion { +// case .failure(_): +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { () in +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + + @Test + func PUTVoidAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _: Void = try await network.put("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + +// func testPUTDataWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (data: Data) in +// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + + @Test + func PUTDataAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let data: Data = try await network.put("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } +// +// func testPUTJSONWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users").sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (json: Any) in +// let data = try? JSONSerialization.data(withJSONObject: json, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// XCTAssertEqual(data, expectedResponseData) +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + + @Test + func PUTJSONAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let json: JSON = try await network.put("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + #expect(data == expectedResponseData) + } +// +// func testPUTNetworkingJSONDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (post: Post) in +// XCTAssertEqual(post.title, "Hello") +// XCTAssertEqual(post.content, "World") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } +// +// func testPUTDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: UserJSON) in +// XCTAssertEqual(userJSON.firstname, "John") +// XCTAssertEqual(userJSON.lastname, "Doe") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + + @Test + func testPUTDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let user: UserJSON = try await network.put("/users/1") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + #expect(user.firstname == "John") + #expect(user.lastname == "Doe") + } + +// func testPUTArrayOfDecodableWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + + @Test + func PUTArrayOfDecodableAsyncWorks() async throws { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let users: [UserJSON] = try await network.put("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(users[0].firstname == "John") + #expect(users[0].lastname == "Doe") + #expect(users[1].firstname == "Jimmy") + #expect(users[1].lastname == "Punchline") + } + +// func testPUTArrayOfDecodableWithKeypathWorks() { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let expectationWorks = expectation(description: "ReceiveValue called") +// let expectationFinished = expectation(description: "Finished called") +// network.put("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// XCTFail() +// case .finished: +// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") +// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") +// expectationFinished.fulfill() +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// XCTAssertEqual(userJSON[0].firstname, "John") +// XCTAssertEqual(userJSON[0].lastname, "Doe") +// XCTAssertEqual(userJSON[1].firstname, "Jimmy") +// XCTAssertEqual(userJSON[1].lastname, "Punchline") +// expectationWorks.fulfill() +// } +// .store(in: &cancellables) +// waitForExpectations(timeout: 0.1) +// } + +} diff --git a/Tests/NetworkingTests/NetworkingTests.swift b/Tests/NetworkingTests/NetworkingTests.swift index f90dbdf..d93ac47 100644 --- a/Tests/NetworkingTests/NetworkingTests.swift +++ b/Tests/NetworkingTests/NetworkingTests.swift @@ -1,31 +1,20 @@ -//import XCTest -//import Networking -//import Combine -// -//final class NetworkingTests: XCTestCase { -// -// var cancellables = Set() -// var cancellable: Cancellable? -// -// func testBadURLDoesntCrash() { -// let exp = expectation(description: "call") -// let client = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") -// client.get("/forge a bad url") -// .sink(receiveCompletion: { completion in -// switch completion { -// case .finished: -// print("finished") -// case .failure(let error): -// if let e = error as? NetworkingError, e.status == .unableToParseRequest { -// exp.fulfill() -// } else { -// exp.fulfill() -// } -// } -// }, -// receiveValue: { (json: Any) in -// print(json) -// }).store(in: &cancellables) -// waitForExpectations(timeout: 1, handler: nil) -// } -//} +import Testing +import Networking +import Combine + +struct NetworkingTests { + + @Test + func badURLDoesntCrash() async { + let client = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") + do { + let json: JSON = try await client.get("/forge a bad url") + } catch { + if let e = error as? NetworkingError, e.status == .unableToParseRequest { + print("OK") + } else { + print("OK2") + } + } + } +} diff --git a/Tests/NetworkingTests/PatchRequestTests.swift b/Tests/NetworkingTests/PatchRequestTests.swift index aea948f..c877746 100644 --- a/Tests/NetworkingTests/PatchRequestTests.swift +++ b/Tests/NetworkingTests/PatchRequestTests.swift @@ -6,60 +6,61 @@ // import Foundation -import XCTest +import Testing import Networking -class PatchRequestTests: XCTestCase { +@Suite +struct PatchRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - override func setUpWithError() throws { + init() { network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } - - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - func testPATCHVoidAsyncWorks() async throws { + + @Test + func PATCHVoidAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ let _:Void = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") } - func testPATCHDataAsyncWorks() async throws { + @Test + func PATCHDataAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ let data: Data = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) } - func testPATCHJSONAsyncWorks() async throws { + @Test + func PATCHJSONAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ {"response":"OK"} """ let json: Any = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") let data = try? JSONSerialization.data(withJSONObject: json, options: []) let expectedResponseData = """ {"response":"OK"} """.data(using: String.Encoding.utf8) - XCTAssertEqual(data, expectedResponseData) + #expect(data == expectedResponseData) } - func testPATCHDecodableAsyncWorks() async throws { + @Test + func PATCHDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { @@ -68,13 +69,14 @@ class PatchRequestTests: XCTestCase { } """ let user: UserJSON = try await network.patch("/users/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - XCTAssertEqual(user.firstname, "John") - XCTAssertEqual(user.lastname, "Doe") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + #expect(user.firstname == "John") + #expect(user.lastname == "Doe") } - func testPATCHArrayOfDecodableAsyncWorks() async throws { + @Test + func PATCHArrayOfDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ [ @@ -89,11 +91,11 @@ class PatchRequestTests: XCTestCase { ] """ let users: [UserJSON] = try await network.patch("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(users[0].firstname == "John") + #expect(users[0].lastname == "Doe") + #expect(users[1].firstname == "Jimmy") + #expect(users[1].lastname == "Punchline") } } diff --git a/Tests/NetworkingTests/PostRequestTests.swift b/Tests/NetworkingTests/PostRequestTests.swift index ab7832d..4df62ab 100644 --- a/Tests/NetworkingTests/PostRequestTests.swift +++ b/Tests/NetworkingTests/PostRequestTests.swift @@ -6,60 +6,59 @@ // import Foundation -import XCTest +import Testing import Networking - -class PostRequestTests: XCTestCase { +@Suite +struct PostRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - override func setUpWithError() throws { + init() { network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - + @Test func testPOSTVoidAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ let _: Void = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") } + @Test func testPOSTDataAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ let data: Data = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) } + @Test func testPOSTJSONAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ {"response":"OK"} """ let json: JSON = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) let expectedResponseData = """ {"response":"OK"} """.data(using: String.Encoding.utf8) - XCTAssertEqual(data, expectedResponseData) + #expect(data == expectedResponseData) } + @Test func testPOSTDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ @@ -69,12 +68,13 @@ class PostRequestTests: XCTestCase { } """ let userJSON:UserJSON = try await network.post("/users/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - XCTAssertEqual(userJSON.firstname, "John") - XCTAssertEqual(userJSON.lastname, "Doe") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") } + @Test func testPOSTArrayOfDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ @@ -90,14 +90,15 @@ class PostRequestTests: XCTestCase { ] """ let users: [UserJSON] = try await network.post("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(users[0].firstname == "John") + #expect(users[0].lastname == "Doe") + #expect(users[1].firstname == "Jimmy") + #expect(users[1].lastname == "Punchline") } + @Test func testAsyncPostEncodable() async throws { MockingURLProtocol.mockedResponse = """ @@ -106,13 +107,13 @@ class PostRequestTests: XCTestCase { let creds = Credentials(username: "john", password: "doe") let data: Data = try await network.post("/users", body: creds) - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() - XCTAssertEqual(body?["username"] as? String, "john") - XCTAssertEqual(body?["password"] as? String, "doe") + #expect(body?["username"] as? String == "john") + #expect(body?["password"] as? String == "doe") } } diff --git a/Tests/NetworkingTests/PutRequestTests.swift b/Tests/NetworkingTests/PutRequestTests.swift index 43b5f2a..e0beaa6 100644 --- a/Tests/NetworkingTests/PutRequestTests.swift +++ b/Tests/NetworkingTests/PutRequestTests.swift @@ -6,198 +6,85 @@ // import Foundation -import XCTest +import Testing import Combine @testable import Networking -class PutRequestTests: XCTestCase { +struct FakeAPI: NetworkingService { + + internal let network = NetworkingClient(baseURL: "https://mocked.com") + + func putUsersVoid() async throws -> Void { + return try await network.put("/users") + +// return try await Request { +// PUT +// "/users" +// Headers([:]) +// Body() +// Parts(file) +// } + // 1 immutable request definition. + // 2 Declarative ,SwuiftUI like syntax immutable request building engine + // 3 Make it work ! + } +} + +@Suite +struct PutRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") private var cancellables = Set() + + private let api = FakeAPI() - override func setUpWithError() throws { + init() { network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } - override func tearDownWithError() throws { - MockingURLProtocol.mockedResponse = "" - MockingURLProtocol.currentRequest = nil - } - -// func testPUTVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.put("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - - func testPUTVoidAsyncWorks() async throws { + @Test + func PUTVoidAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ let _: Void = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") } -// func testPUTDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - - func testPUTDataAsyncWorks() async throws { + @Test + func PUTDataAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ let data: Data = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) } -// -// func testPUTJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - func testPUTJSONAsyncWorks() async throws { + @Test + func PUTJSONAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ {"response":"OK"} """ - let json: Any = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json, options: []) + let json: JSON = try await network.put("/users") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) let expectedResponseData = """ {"response":"OK"} """.data(using: String.Encoding.utf8) - XCTAssertEqual(data, expectedResponseData) + #expect(data == expectedResponseData) } -// -// func testPUTNetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPUTDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - + + @Test func testPUTDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ @@ -207,50 +94,14 @@ class PutRequestTests: XCTestCase { } """ let user: UserJSON = try await network.put("/users/1") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") - XCTAssertEqual(user.firstname, "John") - XCTAssertEqual(user.lastname, "Doe") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + #expect(user.firstname == "John") + #expect(user.lastname == "Doe") } -// func testPUTArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - - func testPUTArrayOfDecodableAsyncWorks() async throws { + @Test + func PUTArrayOfDecodableAsyncWorks() async throws { MockingURLProtocol.mockedResponse = """ [ @@ -265,52 +116,11 @@ class PutRequestTests: XCTestCase { ] """ let users: [UserJSON] = try await network.put("/users") - XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") - XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") - XCTAssertEqual(users[0].firstname, "John") - XCTAssertEqual(users[0].lastname, "Doe") - XCTAssertEqual(users[1].firstname, "Jimmy") - XCTAssertEqual(users[1].lastname, "Punchline") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(users[0].firstname == "John") + #expect(users[0].lastname == "Doe") + #expect(users[1].firstname == "Jimmy") + #expect(users[1].lastname == "Punchline") } - -// func testPUTArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - } From 578dff108193eb607cac597be131cf4185ebaa8a Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 2 Oct 2024 13:59:29 -1000 Subject: [PATCH 11/25] Adds example Swift 6 App --- .../Swift6Arch.xcodeproj/project.pbxproj | 359 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ .../Swift6Arch/Assets.xcassets/Contents.json | 6 + Swift6Arch/Swift6Arch/ContentView.swift | 81 ++++ .../Preview Assets.xcassets/Contents.json | 6 + Swift6Arch/Swift6Arch/Swift6ArchApp.swift | 62 +++ 8 files changed, 567 insertions(+) create mode 100644 Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj create mode 100644 Swift6Arch/Swift6Arch.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Swift6Arch/Swift6Arch/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Swift6Arch/Swift6Arch/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Swift6Arch/Swift6Arch/Assets.xcassets/Contents.json create mode 100644 Swift6Arch/Swift6Arch/ContentView.swift create mode 100644 Swift6Arch/Swift6Arch/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 Swift6Arch/Swift6Arch/Swift6ArchApp.swift diff --git a/Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj b/Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7a59627 --- /dev/null +++ b/Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj @@ -0,0 +1,359 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 639F73972CAE0DF1008287D5 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 639F73962CAE0DF1008287D5 /* Networking */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 639F73832CAE084D008287D5 /* Swift6Arch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Swift6Arch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 639F73942CAE0864008287D5 /* Networking */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Networking; path = /Users/sacha.durand/Projects/Networking; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 639F73852CAE084D008287D5 /* Swift6Arch */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Swift6Arch; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 639F73802CAE084D008287D5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 639F73972CAE0DF1008287D5 /* Networking in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 639F737A2CAE084D008287D5 = { + isa = PBXGroup; + children = ( + 639F73942CAE0864008287D5 /* Networking */, + 639F73852CAE084D008287D5 /* Swift6Arch */, + 639F73842CAE084D008287D5 /* Products */, + ); + sourceTree = ""; + }; + 639F73842CAE084D008287D5 /* Products */ = { + isa = PBXGroup; + children = ( + 639F73832CAE084D008287D5 /* Swift6Arch.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 639F73822CAE084D008287D5 /* Swift6Arch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 639F73912CAE0851008287D5 /* Build configuration list for PBXNativeTarget "Swift6Arch" */; + buildPhases = ( + 639F737F2CAE084D008287D5 /* Sources */, + 639F73802CAE084D008287D5 /* Frameworks */, + 639F73812CAE084D008287D5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 639F73852CAE084D008287D5 /* Swift6Arch */, + ); + name = Swift6Arch; + packageProductDependencies = ( + 639F73962CAE0DF1008287D5 /* Networking */, + ); + productName = Swift6Arch; + productReference = 639F73832CAE084D008287D5 /* Swift6Arch.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 639F737B2CAE084D008287D5 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; + TargetAttributes = { + 639F73822CAE084D008287D5 = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = 639F737E2CAE084D008287D5 /* Build configuration list for PBXProject "Swift6Arch" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 639F737A2CAE084D008287D5; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 639F73952CAE0DF0008287D5 /* XCRemoteSwiftPackageReference "Networking" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 639F73842CAE084D008287D5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 639F73822CAE084D008287D5 /* Swift6Arch */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 639F73812CAE084D008287D5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 639F737F2CAE084D008287D5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 639F738F2CAE0851008287D5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; + }; + name = Debug; + }; + 639F73902CAE0851008287D5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 6.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 639F73922CAE0851008287D5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Swift6Arch/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.sacha.Swift6Arch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 639F73932CAE0851008287D5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Swift6Arch/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.sacha.Swift6Arch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 639F737E2CAE084D008287D5 /* Build configuration list for PBXProject "Swift6Arch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 639F738F2CAE0851008287D5 /* Debug */, + 639F73902CAE0851008287D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 639F73912CAE0851008287D5 /* Build configuration list for PBXNativeTarget "Swift6Arch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 639F73922CAE0851008287D5 /* Debug */, + 639F73932CAE0851008287D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 639F73952CAE0DF0008287D5 /* XCRemoteSwiftPackageReference "Networking" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/freshOS/Networking"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.3; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 639F73962CAE0DF1008287D5 /* Networking */ = { + isa = XCSwiftPackageProductDependency; + package = 639F73952CAE0DF0008287D5 /* XCRemoteSwiftPackageReference "Networking" */; + productName = Networking; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 639F737B2CAE084D008287D5 /* Project object */; +} diff --git a/Swift6Arch/Swift6Arch.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Swift6Arch/Swift6Arch.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Swift6Arch/Swift6Arch.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Swift6Arch/Swift6Arch/Assets.xcassets/AccentColor.colorset/Contents.json b/Swift6Arch/Swift6Arch/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swift6Arch/Swift6Arch/Assets.xcassets/AppIcon.appiconset/Contents.json b/Swift6Arch/Swift6Arch/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swift6Arch/Swift6Arch/Assets.xcassets/Contents.json b/Swift6Arch/Swift6Arch/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swift6Arch/Swift6Arch/ContentView.swift b/Swift6Arch/Swift6Arch/ContentView.swift new file mode 100644 index 0000000..0f1a5a7 --- /dev/null +++ b/Swift6Arch/Swift6Arch/ContentView.swift @@ -0,0 +1,81 @@ +// +// ContentView.swift +// Swift6Arch +// +// Created by DURAND SAINT OMER Sacha on 10/2/24. +// + +import SwiftUI + +@MainActor +@Observable +class ContentViewModel { + + var username = "default" + var isLoading = false + + private let userService: UserService + + init(userService: UserService) { + self.userService = userService + } + + func fetchUser() { + Task { @MainActor [userService] in + do { + isLoading = true + let fetchedUser = try await userService.fetchCurrentUser() + username = fetchedUser.name + isLoading = false + } catch { + isLoading = false + print("error") + } + } + } +} + +struct ContentComponent: View { + + @State var viewModel: ContentViewModel + + init(userService: UserService) { + viewModel = ContentViewModel(userService: userService) + } + + var body: some View { + ContentView(username: viewModel.username, + isLoading: viewModel.isLoading, + didTapFetchUsername: viewModel.fetchUser) + } +} + + +struct ContentView: View { + + let username: String + let isLoading: Bool + let didTapFetchUsername: () -> Void + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + + if isLoading { + ProgressView() + } + Text(username) + Button("Fetch username") { + didTapFetchUsername() + } + } + .padding() + } +} + + +#Preview { + ContentView(username: "John", isLoading: false, didTapFetchUsername: {}) +} diff --git a/Swift6Arch/Swift6Arch/Preview Content/Preview Assets.xcassets/Contents.json b/Swift6Arch/Swift6Arch/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swift6Arch/Swift6Arch/Swift6ArchApp.swift b/Swift6Arch/Swift6Arch/Swift6ArchApp.swift new file mode 100644 index 0000000..0477a0d --- /dev/null +++ b/Swift6Arch/Swift6Arch/Swift6ArchApp.swift @@ -0,0 +1,62 @@ +// +// Swift6ArchApp.swift +// Swift6Arch +// +// Created by DURAND SAINT OMER Sacha on 10/2/24. +// + +import SwiftUI +import Networking + +// Presentation + +@main +struct Swift6ArchApp: App { + + let userService = UserService(userRepository: JSONAPIUserRepository()) + var body: some Scene { + WindowGroup { + ContentComponent(userService: userService) + } + } +} + +// Domain + +class UserService { + + let userRepository: UserRepository + init(userRepository: UserRepository) { + self.userRepository = userRepository + } + + func fetchCurrentUser() async throws -> User { + return try await userRepository.fetchCurrentUser() + } +} + +protocol UserRepository { + func fetchCurrentUser() async throws -> User +} + + +struct User { + let name: String +} + +// Data + +struct JSONAPIUserRepository: UserRepository { + + let network = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") + + func fetchCurrentUser() async throws -> User { + let userJSON: UserJSON = try await network.get("/users/1") + return User(name: userJSON.name) + } +} + + +struct UserJSON: Decodable { + let name: String +} From a9ba0b16d6651bed6d53a499bebdbe0ebd531141 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 2 Oct 2024 20:38:35 -1000 Subject: [PATCH 12/25] WIP - simplify code --- .../Calls/NetworkingClient+Data.swift | 17 +++++++---- .../Calls/NetworkingClient+JSON.swift | 29 ++++++------------- .../Calls/NetworkingClient+Requests.swift | 14 ++++----- .../Calls/NetworkingClient+Void.swift | 17 ++++------- .../NetworkingClient+Data+Combine.swift | 14 ++++----- 5 files changed, 40 insertions(+), 51 deletions(-) diff --git a/Sources/Networking/Calls/NetworkingClient+Data.swift b/Sources/Networking/Calls/NetworkingClient+Data.swift index c5d6ce2..b8de514 100644 --- a/Sources/Networking/Calls/NetworkingClient+Data.swift +++ b/Sources/Networking/Calls/NetworkingClient+Data.swift @@ -10,26 +10,31 @@ import Foundation public extension NetworkingClient { func get(_ route: String, params: Params = Params()) async throws -> Data { - try await execute(request: request(.get, route, params: params)) + try await request(.get, route: route, params: params) } func post(_ route: String, params: Params = Params()) async throws -> Data { - try await execute(request: request(.post, route, params: params)) + try await request(.post, route: route, params: params) } func post(_ route: String, body: Encodable) async throws -> Data { - try await execute(request: request(.post, route, encodableBody: body)) + try await execute(request: createRequest(.post, route, encodableBody: body)) } func put(_ route: String, params: Params = Params()) async throws -> Data { - try await execute(request: request(.put, route, params: params)) + try await request(.put, route: route, params: params) } func patch(_ route: String, params: Params = Params()) async throws -> Data { - try await execute(request: request(.patch, route, params: params)) + try await request(.patch, route: route, params: params) } func delete(_ route: String, params: Params = Params()) async throws -> Data { - try await execute(request: request(.delete, route, params: params)) + try await request(.delete, route: route, params: params) } + + func request(_ httpMethod: HTTPMethod, route: String, params: Params = Params()) async throws -> Data { + try await execute(request: createRequest(httpMethod, route, params: params)) + } + } diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift index d6d729b..93f5b28 100644 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ b/Sources/Networking/Calls/NetworkingClient+JSON.swift @@ -10,64 +10,53 @@ import Foundation public extension NetworkingClient { func get(_ route: String, params: Params = Params()) async throws -> Any { - let req = request(.get, route, params: params) - let data = try await execute(request: req) + let data = try await request(.get, route: route, params: params) let json = try JSONSerialization.jsonObject(with: data, options: []) return json } func get(_ route: String, params: Params = Params()) async throws -> JSON { - let req = request(.get, route, params: params) - let data = try await execute(request: req) - let json = try JSONSerialization.jsonObject(with: data, options: []) - return JSON(jsonObject: json) + return JSON(jsonObject: try await get(route, params: params)) } func post(_ route: String, params: Params = Params()) async throws -> JSON { - let req = request(.post, route, params: params) - let data = try await execute(request: req) + let data = try await request(.post, route: route, params: params) let json = try JSONSerialization.jsonObject(with: data, options: []) return JSON(jsonObject: json) } func post(_ route: String, body: Encodable) async throws -> JSON { - let req = request(.post, route, encodableBody: body) + let req = createRequest(.post, route, encodableBody: body) let data = try await execute(request: req) let json = try JSONSerialization.jsonObject(with: data, options: []) return JSON(jsonObject: json) } func put(_ route: String, params: Params = Params()) async throws -> JSON { - let req = request(.put, route, params: params) - let data = try await execute(request: req) + let data = try await request(.put, route: route, params: params) let json = try JSONSerialization.jsonObject(with: data, options: []) return JSON(jsonObject: json) } func patch(_ route: String, params: Params = Params()) async throws -> Any { - let req = request(.patch, route, params: params) - let data = try await execute(request: req) + let data = try await request(.patch, route: route, params: params) let json = try JSONSerialization.jsonObject(with: data, options: []) return json } func patch(_ route: String, params: Params = Params()) async throws -> JSON { - let req = request(.patch, route, params: params) - let data = try await execute(request: req) - let json = try JSONSerialization.jsonObject(with: data, options: []) - return JSON(jsonObject: json) + return JSON(jsonObject: try await patch(route, params: params)) } func patch(_ route: String, body: Encodable) async throws -> JSON { - let req = request(.patch, route, encodableBody: body) + let req = createRequest(.patch, route, encodableBody: body) let data = try await execute(request: req) let json = try JSONSerialization.jsonObject(with: data, options: []) return JSON(jsonObject: json) } func delete(_ route: String, params: Params = Params()) async throws -> JSON { - let req = request(.delete, route, params: params) - let data = try await execute(request: req) + let data = try await request(.delete, route: route, params: params) let json = try JSONSerialization.jsonObject(with: data, options: []) return JSON(jsonObject: json) } diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index 77c0892..779c1e1 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -10,26 +10,26 @@ import Foundation public extension NetworkingClient { func getRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.get, route, params: params) + createRequest(.get, route, params: params) } func postRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.post, route, params: params) + createRequest(.post, route, params: params) } func putRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.put, route, params: params) + createRequest(.put, route, params: params) } func patchRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.patch, route, params: params) + createRequest(.patch, route, params: params) } func deleteRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.delete, route, params: params) + createRequest(.delete, route, params: params) } - internal func request(_ httpMethod: HTTPMethod, + internal func createRequest(_ httpMethod: HTTPMethod, _ route: String, params: Params = Params() ) -> NetworkingRequest { @@ -45,7 +45,7 @@ public extension NetworkingClient { return req } - internal func request(_ httpMethod: HTTPMethod, + internal func createRequest(_ httpMethod: HTTPMethod, _ route: String, params: Params = Params(), encodableBody: Encodable? = nil diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift index be814f5..c0b9692 100644 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ b/Sources/Networking/Calls/NetworkingClient+Void.swift @@ -10,32 +10,27 @@ import Foundation public extension NetworkingClient { func get(_ route: String, params: Params = Params()) async throws { - let req = request(.get, route, params: params) - _ = try await execute(request: req) + _ = try await request(.get, route: route, params: params) } func post(_ route: String, params: Params = Params()) async throws { - let req = request(.post, route, params: params) - _ = try await execute(request: req) + _ = try await request(.post, route: route, params: params) } func post(_ route: String, body: Encodable) async throws { - let req = request(.post, route, encodableBody: body) + let req = createRequest(.post, route, encodableBody: body) _ = try await execute(request: req) } func put(_ route: String, params: Params = Params()) async throws { - let req = request(.put, route, params: params) - _ = try await execute(request: req) + _ = try await request(.put, route: route, params: params) } func patch(_ route: String, params: Params = Params()) async throws { - let req = request(.patch, route, params: params) - _ = try await execute(request: req) + _ = try await request(.patch, route: route, params: params) } func delete(_ route: String, params: Params = Params()) async throws { - let req = request(.delete, route, params: params) - _ = try await execute(request: req) + _ = try await request(.delete, route: route, params: params) } } diff --git a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift index f6822d4..9789eff 100644 --- a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift +++ b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift @@ -11,30 +11,30 @@ import Combine public extension NetworkingClient { func get(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.get, route, params: params)) + publisher(request: createRequest(.get, route, params: params)) } func post(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.post, route, params: params)) + publisher(request: createRequest(.post, route, params: params)) } func post(_ route: String, body: Encodable) -> AnyPublisher { - publisher(request: request(.post, route, encodableBody: body)) + publisher(request: createRequest(.post, route, encodableBody: body)) } func put(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.put, route, params: params)) + publisher(request: createRequest(.put, route, params: params)) } func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.patch, route, params: params)) + publisher(request: createRequest(.patch, route, params: params)) } func patch(_ route: String, body: Encodable) -> AnyPublisher { - publisher(request: request(.patch, route, encodableBody: body)) + publisher(request: createRequest(.patch, route, encodableBody: body)) } func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: request(.delete, route, params: params)) + publisher(request: createRequest(.delete, route, params: params)) } } From 5af7a971befae02e31b1fb09b23fc6ef51b1acc7 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 2 Oct 2024 21:10:30 -1000 Subject: [PATCH 13/25] WIP - put back combines unit tests --- Package.swift | 6 + .../NetworkingClient+Data+Combine.swift | 14 +- .../NetworkingClient+JSON+Combine.swift | 11 ++ .../Combine/GetRequestTests+Combine.swift | 174 +++++++++--------- 4 files changed, 116 insertions(+), 89 deletions(-) diff --git a/Package.swift b/Package.swift index 572b660..b3c22ae 100644 --- a/Package.swift +++ b/Package.swift @@ -12,4 +12,10 @@ let package = Package( .testTarget(name: "NetworkingTests", dependencies: ["Networking"]) ] ) + + // TODO handle retries +// Amy -> Use plain Sendable for json over JSON +// Put back Combine tests +// try out in real app (Swift 6) + diff --git a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift index 9789eff..107ee42 100644 --- a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift +++ b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift @@ -11,11 +11,11 @@ import Combine public extension NetworkingClient { func get(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: createRequest(.get, route, params: params)) + request(.get, route: route, params: params) } func post(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: createRequest(.post, route, params: params)) + request(.post, route: route, params: params) } func post(_ route: String, body: Encodable) -> AnyPublisher { @@ -23,11 +23,11 @@ public extension NetworkingClient { } func put(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: createRequest(.put, route, params: params)) + request(.put, route: route, params: params) } func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: createRequest(.patch, route, params: params)) + request(.patch, route: route, params: params) } func patch(_ route: String, body: Encodable) -> AnyPublisher { @@ -35,6 +35,10 @@ public extension NetworkingClient { } func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - publisher(request: createRequest(.delete, route, params: params)) + request(.delete, route: route, params: params) + } + + func request(_ httpMethod: HTTPMethod, route: String, params: Params = Params()) -> AnyPublisher { + publisher(request: createRequest(httpMethod, route, params: params)) } } diff --git a/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift b/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift index e234a83..e35f5af 100644 --- a/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift +++ b/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift @@ -9,6 +9,10 @@ import Foundation import Combine public extension NetworkingClient { + + func get(_ route: String, params: Params = Params()) -> AnyPublisher { + get(route, params: params).toJSONAny() + } func get(_ route: String, params: Params = Params()) -> AnyPublisher { get(route, params: params).toJSON() @@ -50,5 +54,12 @@ extension Publisher where Output == Data { return JSON(jsonObject: json) }.eraseToAnyPublisher() } + + public func toJSONAny() -> AnyPublisher { + tryMap { data -> Any in + let json = try JSONSerialization.jsonObject(with: data, options: []) + return json + }.eraseToAnyPublisher() + } } diff --git a/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift index 9629da6..3a5eeee 100644 --- a/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift @@ -13,95 +13,101 @@ import Combine import Networking @Suite(.serialized) -struct GetRequestCombineTests { +class GetRequestCombineTests { private let network = NetworkingClient(baseURL: "https://mocked.com") private var cancellables = Set() - init() async { - await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + @Test + func GETVoidPublisher() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + + let result = await withCheckedContinuation { continuation in + network.get("/users").sink { completion in + switch completion { + case .failure(_): + Issue.record("Call failed") + case .finished: + continuation.resume(returning: "done") + } + } receiveValue: { () in + + } + .store(in: &cancellables) + } + #expect(result == "done") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + + } + + @Test + func GETDataPublisher() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let result = await withCheckedContinuation { continuation in + network.get("/users").sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (data: Data) in + continuation.resume(returning: data) + } + .store(in: &cancellables) + } + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(result == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + + } + + func foo() -> AnyPublisher { + return network.get("/users") + } + + @Test + func GETJSONPublisher() async { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + + let result = await withCheckedContinuation { continuation in + network.get("/users").sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (json: Sendable) in + continuation.resume(returning: json) + } + .store(in: &cancellables) + } + + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: result, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + + #expect(data == expectedResponseData) } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } -// -// func testGETVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.get("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// -// func testGETDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// func testGETJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } // // func testGETNetworkingJSONDecodableWorks() { // MockingURLProtocol.mockedResponse = From 0aa5bd324a3a4e7de82aa56f8a815b686817a638 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Thu, 3 Oct 2024 13:35:37 -1000 Subject: [PATCH 14/25] Update GetRequestTests+Combine.swift --- .../Combine/GetRequestTests+Combine.swift | 270 +++++++++--------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift index 3a5eeee..61fcef6 100644 --- a/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift @@ -108,141 +108,141 @@ class GetRequestCombineTests { #expect(data == expectedResponseData) } -// -// func testGETNetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } + + @Test + func GETNetworkingJSONDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "title":"Hello", + "content":"World", + } + """ + let post = await withCheckedContinuation { continuation in + network.get("/posts/1") + .sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (post: Post) in + continuation.resume(returning: post) + } + .store(in: &cancellables) + } + #expect(post.title == "Hello") + #expect(post.content == "World") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") + } -// func testGETDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// func testGETArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// -// -// func testGETArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.get("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } + @Test + func GETDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let userJSON = await withCheckedContinuation { continuation in + network.get("/users/1") + .sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (userJSON: UserJSON) in + continuation.resume(returning: userJSON) + } + .store(in: &cancellables) + } + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + } + + @Test + func GETArrayOfDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let userJSON = await withCheckedContinuation { continuation in + network.get("/users") + .sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (userJSON: [UserJSON]) in + continuation.resume(returning: userJSON) + } + .store(in: &cancellables) + } + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + + } + + @Test + func GETArrayOfDecodableWithKeypathWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "users" : + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + } + """ + let userJSON = await withCheckedContinuation { continuation in + network.get("/users", keypath: "users") + .sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (userJSON: [UserJSON]) in + continuation.resume(returning: userJSON) + } + .store(in: &cancellables) + } + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } } From 0e741915cb866c4d925e6c43dbeb1cbf2d517bf3 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Thu, 3 Oct 2024 13:47:25 -1000 Subject: [PATCH 15/25] WIP - updates DeleteRequestCombineTests --- .../NetworkingClient+JSON+Combine.swift | 8 +- .../Combine/DeleteRequestTests+Combine.swift | 174 +++++++++--------- .../NetworkingTests/DeleteRequestTests.swift | 4 +- Tests/NetworkingTests/NetworkingTests.swift | 2 +- 4 files changed, 95 insertions(+), 93 deletions(-) diff --git a/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift b/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift index e35f5af..b3987eb 100644 --- a/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift +++ b/Sources/Networking/Combine/NetworkingClient+JSON+Combine.swift @@ -11,7 +11,7 @@ import Combine public extension NetworkingClient { func get(_ route: String, params: Params = Params()) -> AnyPublisher { - get(route, params: params).toJSONAny() + get(route, params: params).toJSONSendable() } func get(_ route: String, params: Params = Params()) -> AnyPublisher { @@ -37,6 +37,10 @@ public extension NetworkingClient { func patch(_ route: String, body: Encodable) -> AnyPublisher { patch(route, body: body).toJSON() } + + func delete(_ route: String, params: Params = Params()) -> AnyPublisher { + delete(route, params: params).toJSONSendable() + } func delete(_ route: String, params: Params = Params()) -> AnyPublisher { delete(route, params: params).toJSON() @@ -55,7 +59,7 @@ extension Publisher where Output == Data { }.eraseToAnyPublisher() } - public func toJSONAny() -> AnyPublisher { + public func toJSONSendable() -> AnyPublisher { tryMap { data -> Any in let json = try JSONSerialization.jsonObject(with: data, options: []) return json diff --git a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift index 32cbe4a..facd320 100644 --- a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift @@ -6,99 +6,97 @@ // import Testing +import Combine import Foundation import Networking -@Suite -struct DeleteRequestCombineTests { +@Suite(.serialized) +class DeleteRequestCombineTests { -// private let network = NetworkingClient(baseURL: "https://mocked.com") -//// private var cancellables = Set() -// -// init() { -// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] -// } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } + private let network = NetworkingClient(baseURL: "https://mocked.com") + private var cancellables = Set() -// func testDELETEVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.delete("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// func testDELETEJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } + init() { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + @Test + func DELETEVoidWorks() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + _ = await withCheckedContinuation { continuation in + network.delete("/users").sink { completion in + switch completion { + case .failure(_): + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { () in + continuation.resume(returning: ()) + } + .store(in: &cancellables) + } + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + + @Test + func DELETEDataWorks() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let result = await withCheckedContinuation { continuation in + network.delete("/users").sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (data: Data) in + continuation.resume(returning: data) + } + .store(in: &cancellables) + } + #expect(result != nil) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + + @Test + func DELETEJSONWorks() async { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let result = await withCheckedContinuation { continuation in + network.delete("/users").sink { completion in + switch completion { + case .failure: + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { (json: Sendable) in + continuation.resume(returning: json) + } + .store(in: &cancellables) + } + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: result, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + + #expect(data == expectedResponseData) + } + // func testDELETENetworkingJSONDecodableWorks() { // MockingURLProtocol.mockedResponse = // """ diff --git a/Tests/NetworkingTests/DeleteRequestTests.swift b/Tests/NetworkingTests/DeleteRequestTests.swift index aabff7b..8655bca 100644 --- a/Tests/NetworkingTests/DeleteRequestTests.swift +++ b/Tests/NetworkingTests/DeleteRequestTests.swift @@ -14,8 +14,8 @@ struct DeleteRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - init() async { - await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } @Test diff --git a/Tests/NetworkingTests/NetworkingTests.swift b/Tests/NetworkingTests/NetworkingTests.swift index d93ac47..d731f58 100644 --- a/Tests/NetworkingTests/NetworkingTests.swift +++ b/Tests/NetworkingTests/NetworkingTests.swift @@ -8,7 +8,7 @@ struct NetworkingTests { func badURLDoesntCrash() async { let client = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") do { - let json: JSON = try await client.get("/forge a bad url") + let _: JSON = try await client.get("/forge a bad url") } catch { if let e = error as? NetworkingError, e.status == .unableToParseRequest { print("OK") From d12fab33ddc57735741c849d20d1c51ff9b6dfdb Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Fri, 4 Oct 2024 13:40:54 -1000 Subject: [PATCH 16/25] Update DeleteRequestTests+Combine.swift --- .../Combine/DeleteRequestTests+Combine.swift | 276 +++++++----------- 1 file changed, 102 insertions(+), 174 deletions(-) diff --git a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift index facd320..9ed5482 100644 --- a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift @@ -26,19 +26,8 @@ class DeleteRequestCombineTests { """ { "response": "OK" } """ - _ = await withCheckedContinuation { continuation in - network.delete("/users").sink { completion in - switch completion { - case .failure(_): - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { () in - continuation.resume(returning: ()) - } - .store(in: &cancellables) - } + + let void: Void = await testHelper(network.delete("/users")) #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") } @@ -49,20 +38,8 @@ class DeleteRequestCombineTests { """ { "response": "OK" } """ - let result = await withCheckedContinuation { continuation in - network.delete("/users").sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (data: Data) in - continuation.resume(returning: data) - } - .store(in: &cancellables) - } - #expect(result != nil) + let data: Data = await testHelper(network.delete("/users")) + #expect(data != nil) #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") } @@ -73,163 +50,114 @@ class DeleteRequestCombineTests { """ {"response":"OK"} """ - let result = await withCheckedContinuation { continuation in - network.delete("/users").sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (json: Sendable) in - continuation.resume(returning: json) - } - .store(in: &cancellables) - } + let json: JSON = await testHelper(network.delete("/users")) #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: result, options: []) + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) let expectedResponseData = """ {"response":"OK"} """.data(using: String.Encoding.utf8) - #expect(data == expectedResponseData) } + // Todo put back Sendable version -// func testDELETENetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } + @Test + func testDELETENetworkingJSONDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "title":"Hello", + "content":"World", + } + """ + let post: Post = await testHelper(network.delete("/posts/1")) + #expect(post.title == "Hello") + #expect(post.content == "World") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") + } -// func testDELETEDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testDELETEArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } + @Test + func testDELETEDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let userJSON: UserJSON = await testHelper(network.delete("/users/1")) + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + } -// func testDELETEArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.delete("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } + @Test + func testDELETEArrayOfDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let userJSON: [UserJSON] = await testHelper(network.delete("/users")) + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + @Test + func testDELETEArrayOfDecodableWithKeypathWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "users" : + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + } + """ + let userJSON: [UserJSON] = await testHelper(network.delete("/users", keypath: "users")) + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + + func testHelper(_ publisher: AnyPublisher) async -> T { + return await withCheckedContinuation { continuation in + publisher.sink { completion in + switch completion { + case .failure(_): + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { x in + continuation.resume(returning: x) + } + .store(in: &cancellables) + } + } } From 0c7033a87912e4b2e44a11b12ef8cf3979354137 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Sat, 5 Oct 2024 16:27:09 -1000 Subject: [PATCH 17/25] Update PutRequestTests+Combine.swift --- .../Combine/PutRequestTests+Combine.swift | 316 +++++------------- 1 file changed, 81 insertions(+), 235 deletions(-) diff --git a/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift index 03c04b8..365ba62 100644 --- a/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift @@ -13,7 +13,7 @@ import Combine import Networking @Suite -struct PutRequestCombineTests { +class PutRequestCombineTests { private let network = NetworkingClient(baseURL: "https://mocked.com") private var cancellables = Set() @@ -22,184 +22,65 @@ struct PutRequestCombineTests { network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } - -// func testPUTVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.put("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - @Test - func PUTVoidAsyncWorks() async throws { + func PUTVoidWorks() async { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ - let _: Void = try await network.put("/users") + let _: Void = await testHelper(network.put("/users")) #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") } -// func testPUTDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - @Test - func PUTDataAsyncWorks() async throws { + func PUTDataWorks() async { MockingURLProtocol.mockedResponse = """ { "response": "OK" } """ - let data: Data = try await network.put("/users") + let data: Data = await testHelper(network.put("/users")) #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) } -// -// func testPUTJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } @Test - func PUTJSONAsyncWorks() async throws { + func testPUTJSONWorks() async { MockingURLProtocol.mockedResponse = """ {"response":"OK"} """ - let json: JSON = try await network.put("/users") + let json: JSON = await testHelper(network.put("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) #expect(data == expectedResponseData) } -// -// func testPUTNetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPUTDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } + + @Test + func PUTNetworkingJSONDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "title":"Hello", + "content":"World", + } + """ + let post: Post = await testHelper(network.put("/posts/1")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") + #expect(post.title == "Hello") + #expect(post.content == "World") + } @Test - func testPUTDecodableAsyncWorks() async throws { + func PUTDecodableWorks() async { MockingURLProtocol.mockedResponse = """ { @@ -207,52 +88,15 @@ struct PutRequestCombineTests { "lastname":"Doe", } """ - let user: UserJSON = try await network.put("/users/1") + let userJSON: UserJSON = await testHelper(network.put("/users/1")) #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") - #expect(user.firstname == "John") - #expect(user.lastname == "Doe") + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") } -// func testPUTArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - @Test - func PUTArrayOfDecodableAsyncWorks() async throws { + func PUTArrayOfDecodableWorks() async { MockingURLProtocol.mockedResponse = """ [ @@ -266,53 +110,55 @@ struct PutRequestCombineTests { } ] """ - let users: [UserJSON] = try await network.put("/users") + let userJSON: [UserJSON] = await testHelper(network.put("/users")) #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(users[0].firstname == "John") - #expect(users[0].lastname == "Doe") - #expect(users[1].firstname == "Jimmy") - #expect(users[1].lastname == "Punchline") + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + } + + @Test + func PUTArrayOfDecodableWithKeypathWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "users" : + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + } + """ + let userJSON: [UserJSON] = await testHelper(network.put("/users", keypath: "users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + } + + func testHelper(_ publisher: AnyPublisher) async -> T { + return await withCheckedContinuation { continuation in + publisher.sink { completion in + switch completion { + case .failure(_): + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { x in + continuation.resume(returning: x) + } + .store(in: &cancellables) + } } - -// func testPUTArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.put("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PUT") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } - } From e565a290cd636880ddb0f53c4a91d2a9d76d5be2 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Sat, 5 Oct 2024 16:45:05 -1000 Subject: [PATCH 18/25] Update PostRequestTests+Combine.swift --- .../Combine/PostRequestTests+Combine.swift | 446 +++++++----------- 1 file changed, 176 insertions(+), 270 deletions(-) diff --git a/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift index a3e05c5..95a098a 100644 --- a/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift @@ -1,273 +1,179 @@ -//// -//// PostRequestTests.swift -//// -//// -//// Created by Sacha DSO on 12/04/2022. -//// // -//import Foundation -//import XCTest -//import Combine +// PostRequestTests.swift +// // -//@testable -//import Networking +// Created by Sacha DSO on 12/04/2022. // -// -//class PostRequestTests: XCTestCase { -// -// private let network = NetworkingClient(baseURL: "https://mocked.com") -// private var cancellables = Set() -// -// override func setUpWithError() throws { -// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] -// } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } -// -// func testPOSTVoidWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "Call works") -// let expectationFinished = expectation(description: "Finished") -// network.post("/users").sink { completion in -// switch completion { -// case .failure(_): -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { () in -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTDataWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTJSONWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// {"response":"OK"} -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users").sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (json: Any) in -// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -// let expectedResponseData = -// """ -// {"response":"OK"} -// """.data(using: String.Encoding.utf8) -// -// XCTAssertEqual(data, expectedResponseData) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTNetworkingJSONDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "title":"Hello", -// "content":"World", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/posts/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (post: Post) in -// XCTAssertEqual(post.title, "Hello") -// XCTAssertEqual(post.content, "World") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "firstname":"John", -// "lastname":"Doe", -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users/1") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: UserJSON) in -// XCTAssertEqual(userJSON.firstname, "John") -// XCTAssertEqual(userJSON.lastname, "Doe") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTArrayOfDecodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTArrayOfDecodableWithKeypathWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { -// "users" : -// [ -// { -// "firstname":"John", -// "lastname":"Doe" -// }, -// { -// "firstname":"Jimmy", -// "lastname":"Punchline" -// } -// ] -// } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// network.post("/users", keypath: "users") -// .sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// expectationFinished.fulfill() -// } -// } receiveValue: { (userJSON: [UserJSON]) in -// XCTAssertEqual(userJSON[0].firstname, "John") -// XCTAssertEqual(userJSON[0].lastname, "Doe") -// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -// XCTAssertEqual(userJSON[1].lastname, "Punchline") -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -// func testPOSTDataEncodableWorks() { -// MockingURLProtocol.mockedResponse = -// """ -// { "response": "OK" } -// """ -// let expectationWorks = expectation(description: "ReceiveValue called") -// let expectationFinished = expectation(description: "Finished called") -// -// let creds = Credentials(username: "Alan", password: "Turing") -// network.post("/users", body: creds).sink { completion in -// switch completion { -// case .failure: -// XCTFail() -// case .finished: -// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") -// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -// -// let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() -// XCTAssertEqual(body?["username"] as? String, "Alan") -// XCTAssertEqual(body?["password"] as? String, "Turing") -// -// expectationFinished.fulfill() -// } -// } receiveValue: { (data: Data) in -// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -// expectationWorks.fulfill() -// } -// .store(in: &cancellables) -// waitForExpectations(timeout: 0.1) -// } -// -//} + +import Foundation +import Testing +import Combine + +@testable +import Networking + +@Suite +class PostRequestCombineTests { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + private var cancellables = Set() + + init() { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + @Test + func POSTVoidWorks() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _: Void = await testHelper(network.post("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + + @Test + func POSTDataWorks() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let data: Data = await testHelper(network.post("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } + + @Test + func POSTJSONWorks() async { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let json: JSON = await testHelper(network.post("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + #expect(data == expectedResponseData) + } + + @Test + func POSTNetworkingJSONDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "title":"Hello", + "content":"World", + } + """ + let post: Post = await testHelper(network.post("/posts/1")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") + #expect(post.title == "Hello") + #expect(post.content == "World") + } + + @Test + func POSTDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let userJSON: UserJSON = await testHelper(network.post("/users/1")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") + } + + @Test + func POSTArrayOfDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let userJSON: [UserJSON] = await testHelper(network.post("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + } + + @Test + func POSTArrayOfDecodableWithKeypathWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "users" : + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + } + """ + let userJSON: [UserJSON] = await testHelper(network.post("/users", keypath: "users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + } + + @Test + func POSTDataEncodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let creds = Credentials(username: "Alan", password: "Turing") + let data: Data = await testHelper(network.post("/users", body: creds)) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() + #expect(body?["username"] as? String == "Alan") + #expect(body?["password"] as? String == "Turing") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } + + func testHelper(_ publisher: AnyPublisher) async -> T { + return await withCheckedContinuation { continuation in + publisher.sink { completion in + switch completion { + case .failure(_): + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { x in + continuation.resume(returning: x) + } + .store(in: &cancellables) + } + } +} From d6080bc99dc4fa28232fa7fa18f58b96133a9d27 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Sat, 5 Oct 2024 17:05:03 -1000 Subject: [PATCH 19/25] Puts back PatchRequestCombineTests --- .../Combine/DeleteRequestTests+Combine.swift | 2 +- .../Combine/PatchRequestTests+Combine.swift | 392 +++++++----------- .../Combine/PostRequestTests+Combine.swift | 2 +- .../Combine/PutRequestTests+Combine.swift | 2 +- 4 files changed, 163 insertions(+), 235 deletions(-) diff --git a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift index 9ed5482..472ab9f 100644 --- a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift @@ -27,7 +27,7 @@ class DeleteRequestCombineTests { { "response": "OK" } """ - let void: Void = await testHelper(network.delete("/users")) + let _: Void = await testHelper(network.delete("/users")) #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") } diff --git a/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift index d49cd7e..e5e6b9d 100644 --- a/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift @@ -1,235 +1,163 @@ -//// -//// PatchRequestTests.swift -//// -//// -//// Created by Sacha DSO on 12/04/2022. -//// // -//import Foundation -//import XCTest -//import Combine +// PatchRequestTests.swift +// // -//@testable -//import Networking +// Created by Sacha DSO on 12/04/2022. // -//class PatchRequestTests: XCTestCase { -// -// private let network = NetworkingClient(baseURL: "https://mocked.com") -// private var cancellables = Set() -// -// override func setUpWithError() throws { -// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] -// } -// -// override func tearDownWithError() throws { -// MockingURLProtocol.mockedResponse = "" -// MockingURLProtocol.currentRequest = nil -// } -// -//// func testPATCHVoidWorks() { -//// MockingURLProtocol.mockedResponse = -//// """ -//// { "response": "OK" } -//// """ -//// let expectationWorks = expectation(description: "Call works") -//// let expectationFinished = expectation(description: "Finished") -//// network.patch("/users").sink { completion in -//// switch completion { -//// case .failure(_): -//// XCTFail() -//// case .finished: -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -//// expectationFinished.fulfill() -//// } -//// } receiveValue: { () in -//// expectationWorks.fulfill() -//// } -//// .store(in: &cancellables) -//// waitForExpectations(timeout: 0.1) -//// } -//// func testPATCHDataWorks() { -//// MockingURLProtocol.mockedResponse = -//// """ -//// { "response": "OK" } -//// """ -//// let expectationWorks = expectation(description: "ReceiveValue called") -//// let expectationFinished = expectation(description: "Finished called") -//// network.patch("/users").sink { completion in -//// switch completion { -//// case .failure: -//// XCTFail() -//// case .finished: -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -//// expectationFinished.fulfill() -//// } -//// } receiveValue: { (data: Data) in -//// XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) -//// expectationWorks.fulfill() -//// } -//// .store(in: &cancellables) -//// waitForExpectations(timeout: 0.1) -//// } -//// func testPATCHJSONWorks() { -//// MockingURLProtocol.mockedResponse = -//// """ -//// {"response":"OK"} -//// """ -//// let expectationWorks = expectation(description: "ReceiveValue called") -//// let expectationFinished = expectation(description: "Finished called") -//// network.patch("/users").sink { completion in -//// switch completion { -//// case .failure: -//// XCTFail() -//// case .finished: -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -//// expectationFinished.fulfill() -//// } -//// } receiveValue: { (json: Any) in -//// let data = try? JSONSerialization.data(withJSONObject: json, options: []) -//// let expectedResponseData = -//// """ -//// {"response":"OK"} -//// """.data(using: String.Encoding.utf8) -//// -//// XCTAssertEqual(data, expectedResponseData) -//// expectationWorks.fulfill() -//// } -//// .store(in: &cancellables) -//// waitForExpectations(timeout: 0.1) -//// } -//// func testPATCHNetworkingJSONDecodableWorks() { -//// MockingURLProtocol.mockedResponse = -//// """ -//// { -//// "title":"Hello", -//// "content":"World", -//// } -//// """ -//// let expectationWorks = expectation(description: "ReceiveValue called") -//// let expectationFinished = expectation(description: "Finished called") -//// network.patch("/posts/1") -//// .sink { completion in -//// switch completion { -//// case .failure: -//// XCTFail() -//// case .finished: -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1") -//// expectationFinished.fulfill() -//// } -//// } receiveValue: { (post: Post) in -//// XCTAssertEqual(post.title, "Hello") -//// XCTAssertEqual(post.content, "World") -//// expectationWorks.fulfill() -//// } -//// .store(in: &cancellables) -//// waitForExpectations(timeout: 0.1) -//// } -//// func testPATCHDecodableWorks() { -//// MockingURLProtocol.mockedResponse = -//// """ -//// { -//// "firstname":"John", -//// "lastname":"Doe", -//// } -//// """ -//// let expectationWorks = expectation(description: "ReceiveValue called") -//// let expectationFinished = expectation(description: "Finished called") -//// network.patch("/users/1") -//// .sink { completion in -//// switch completion { -//// case .failure: -//// XCTFail() -//// case .finished: -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1") -//// expectationFinished.fulfill() -//// } -//// } receiveValue: { (userJSON: UserJSON) in -//// XCTAssertEqual(userJSON.firstname, "John") -//// XCTAssertEqual(userJSON.lastname, "Doe") -//// expectationWorks.fulfill() -//// } -//// .store(in: &cancellables) -//// waitForExpectations(timeout: 0.1) -//// } -//// func testPATCHArrayOfDecodableWorks() { -//// MockingURLProtocol.mockedResponse = -//// """ -//// [ -//// { -//// "firstname":"John", -//// "lastname":"Doe" -//// }, -//// { -//// "firstname":"Jimmy", -//// "lastname":"Punchline" -//// } -//// ] -//// """ -//// let expectationWorks = expectation(description: "ReceiveValue called") -//// let expectationFinished = expectation(description: "Finished called") -//// network.patch("/users") -//// .sink { completion in -//// switch completion { -//// case .failure: -//// XCTFail() -//// case .finished: -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -//// expectationFinished.fulfill() -//// } -//// } receiveValue: { (userJSON: [UserJSON]) in -//// XCTAssertEqual(userJSON[0].firstname, "John") -//// XCTAssertEqual(userJSON[0].lastname, "Doe") -//// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -//// XCTAssertEqual(userJSON[1].lastname, "Punchline") -//// expectationWorks.fulfill() -//// } -//// .store(in: &cancellables) -//// waitForExpectations(timeout: 0.1) -//// } -//// func testPATCHArrayOfDecodableWithKeypathWorks() { -//// MockingURLProtocol.mockedResponse = -//// """ -//// { -//// "users" : -//// [ -//// { -//// "firstname":"John", -//// "lastname":"Doe" -//// }, -//// { -//// "firstname":"Jimmy", -//// "lastname":"Punchline" -//// } -//// ] -//// } -//// """ -//// let expectationWorks = expectation(description: "ReceiveValue called") -//// let expectationFinished = expectation(description: "Finished called") -//// network.patch("/users", keypath: "users") -//// .sink { completion in -//// switch completion { -//// case .failure: -//// XCTFail() -//// case .finished: -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "PATCH") -//// XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") -//// expectationFinished.fulfill() -//// } -//// } receiveValue: { (userJSON: [UserJSON]) in -//// XCTAssertEqual(userJSON[0].firstname, "John") -//// XCTAssertEqual(userJSON[0].lastname, "Doe") -//// XCTAssertEqual(userJSON[1].firstname, "Jimmy") -//// XCTAssertEqual(userJSON[1].lastname, "Punchline") -//// expectationWorks.fulfill() -//// } -//// .store(in: &cancellables) -//// waitForExpectations(timeout: 0.1) -//// } -// -//} + +import Foundation +import Testing +import Combine + +@testable +import Networking + +@Suite(.serialized) +class PatchRequestCombineTests { + + private let network = NetworkingClient(baseURL: "https://mocked.com") + private var cancellables = Set() + + init() { + network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + } + + @Test + func PATCHVoidWorks() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let _: Void = await testHelper(network.patch("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + } + + @Test + func PATCHDataWorks() async { + MockingURLProtocol.mockedResponse = + """ + { "response": "OK" } + """ + let data: Data = await testHelper(network.patch("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) + } + + @Test + func PATCHJSONWorks() async { + MockingURLProtocol.mockedResponse = + """ + {"response":"OK"} + """ + let json: JSON = await testHelper(network.patch("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) + let expectedResponseData = + """ + {"response":"OK"} + """.data(using: String.Encoding.utf8) + #expect(data == expectedResponseData) + } + + @Test + func testPATCHNetworkingJSONDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "title":"Hello", + "content":"World", + } + """ + let post: Post = await testHelper(network.patch("/posts/1")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") + #expect(post.title == "Hello") + #expect(post.content == "World") + } + + @Test + func PATCHDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "firstname":"John", + "lastname":"Doe", + } + """ + let userJSON: UserJSON = await testHelper(network.patch("/users/1")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") + #expect(userJSON.firstname == "John") + #expect(userJSON.lastname == "Doe") + } + + @Test + func PATCHArrayOfDecodableWorks() async { + MockingURLProtocol.mockedResponse = + """ + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + """ + let userJSON: [UserJSON] = await testHelper(network.patch("/users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + } + + @Test + func testPATCHArrayOfDecodableWithKeypathWorks() async { + MockingURLProtocol.mockedResponse = + """ + { + "users" : + [ + { + "firstname":"John", + "lastname":"Doe" + }, + { + "firstname":"Jimmy", + "lastname":"Punchline" + } + ] + } + """ + let userJSON: [UserJSON] = await testHelper(network.patch("/users", keypath: "users")) + #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") + #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") + #expect(userJSON[0].firstname == "John") + #expect(userJSON[0].lastname == "Doe") + #expect(userJSON[1].firstname == "Jimmy") + #expect(userJSON[1].lastname == "Punchline") + } + + func testHelper(_ publisher: AnyPublisher) async -> T { + return await withCheckedContinuation { continuation in + publisher.sink { completion in + switch completion { + case .failure(_): + Issue.record("failure") + case .finished: + print("finished") + } + } receiveValue: { x in + continuation.resume(returning: x) + } + .store(in: &cancellables) + } + } +} diff --git a/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift index 95a098a..fd8b9eb 100644 --- a/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift @@ -12,7 +12,7 @@ import Combine @testable import Networking -@Suite +@Suite(.serialized) class PostRequestCombineTests { private let network = NetworkingClient(baseURL: "https://mocked.com") diff --git a/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift index 365ba62..51293c8 100644 --- a/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift @@ -12,7 +12,7 @@ import Combine @testable import Networking -@Suite +@Suite(.serialized) class PutRequestCombineTests { private let network = NetworkingClient(baseURL: "https://mocked.com") From 3eb5e34bf95cc0a9f46bf38164c68de4b3918164 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Sat, 5 Oct 2024 17:12:25 -1000 Subject: [PATCH 20/25] Puts back MultipartRequestTests --- Sources/Networking/NetworkingRequest.swift | 2 +- .../MultipartRequestTests.swift | 227 +++++++++--------- 2 files changed, 117 insertions(+), 112 deletions(-) diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index 48bbff9..f03246e 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -14,7 +14,7 @@ public struct NetworkingRequest { let params: Params let encodableBody: Encodable? let headers: [String: String] - let multipartData: [MultipartData]? + var multipartData: [MultipartData]? let timeout: TimeInterval? let maxRetryCount = 3 } diff --git a/Tests/NetworkingTests/MultipartRequestTests.swift b/Tests/NetworkingTests/MultipartRequestTests.swift index d21c670..d6cb073 100644 --- a/Tests/NetworkingTests/MultipartRequestTests.swift +++ b/Tests/NetworkingTests/MultipartRequestTests.swift @@ -1,114 +1,119 @@ -//// -//// MultipartRequestTests.swift -//// -//// -//// Created by Jeff Barg on 7/22/20. -//// // -//import Foundation -//import XCTest -//import Combine +// MultipartRequestTests.swift +// // -//@testable -//import Networking +// Created by Jeff Barg on 7/22/20. // -//final class MultipartRequestTests: XCTestCase { -// let baseClient: NetworkingClient = NetworkingClient(baseURL: "https://example.com/") -// let route = "/api/test" -// -// func testRequestGenerationWithSingleFile() { -// // Set up test -// let params: Params = [:] -// let multipartData = MultipartData(name: "test_name", -// fileData: "test data".data(using: .utf8)!, -// fileName: "file.txt", -// mimeType: "text/plain") -// -// // Construct request -// let request = baseClient.request(.post, route, params: params) -// request.multipartData = [multipartData] -// -// if let urlRequest = request.buildURLRequest(), -// let body = urlRequest.httpBody, -// let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { -// // Extract boundary from header -// XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) -// let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") -// -// // Test correct body construction -// let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + -// "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" -// let actualBody = String(data: body, encoding: .utf8) -// XCTAssertEqual(actualBody, expectedBody) -// } else { -// XCTFail("Properly-formed URL request was not constructed") -// } -// } -// -// func testRequestGenerationWithParams() { -// // Set up test -// let params: Params = ["test_name": "test_value"] -// let multipartData = MultipartData(name: "test_name", -// fileData: "test data".data(using: .utf8)!, -// fileName: "file.txt", -// mimeType: "text/plain") -// -// // Construct request -// let request = baseClient.request(.post, route, params: params) -// request.multipartData = [multipartData] -// -// if let urlRequest = request.buildURLRequest(), -// let body = urlRequest.httpBody, -// let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { -// // Extract boundary from header -// XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) -// let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") -// -// // Test correct body construction -// let expectedBody = "--\(boundary)\r\nContent-Disposition: " + -// "form-data; name=\"test_name\"\r\n\r\ntest_value\r\n--\(boundary)\r\nContent-Disposition: form-data; " + -// "name=\"test_name\"; filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" -// let actualBody = String(data: body, encoding: .utf8) -// XCTAssertEqual(actualBody, expectedBody) -// } else { -// XCTFail("Properly-formed URL request was not constructed") -// } -// } -// -// func testRequestGenerationWithMultipleFiles() { -// // Set up test -// let params: Params = [:] -// let multipartData = [ -// MultipartData(name: "test_name", -// fileData: "test data".data(using: .utf8)!, -// fileName: "file.txt", -// mimeType: "text/plain"), -// MultipartData(name: "second_name", -// fileData: "another file".data(using: .utf8)!, -// fileName: "file2.txt", -// mimeType: "text/plain") -// ] -// -// // Construct request -// let request = baseClient.request(.post, route, params: params) -// request.multipartData = multipartData -// -// if let urlRequest = request.buildURLRequest(), -// let body = urlRequest.httpBody, -// let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { -// // Extract boundary from header -// XCTAssert(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) -// let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") -// -// // Test correct body construction -// let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + -// "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest " + -// "data\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"second_name\"; " + -// "filename=\"file2.txt\"\r\nContent-Type: text/plain\r\n\r\nanother file\r\n--\(boundary)--" -// let actualBody = String(data: body, encoding: .utf8) -// XCTAssertEqual(actualBody, expectedBody) -// } else { -// XCTFail("Properly-formed URL request was not constructed") -// } -// } -//} + +import Foundation +import Testing +import Combine + +@testable +import Networking + +@Suite +final class MultipartRequestTests { + + let baseClient: NetworkingClient = NetworkingClient(baseURL: "https://example.com/") + let route = "/api/test" + + @Test + func RequestGenerationWithSingleFile() async { + // Set up test + let params: Params = [:] + let multipartData = MultipartData(name: "test_name", + fileData: "test data".data(using: .utf8)!, + fileName: "file.txt", + mimeType: "text/plain") + + // Construct request + var request = baseClient.createRequest(.post, route, params: params) + request.multipartData = [multipartData] + + if let urlRequest = request.buildURLRequest(), + let body = urlRequest.httpBody, + let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { + // Extract boundary from header + #expect(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) + let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") + + // Test correct body construction + let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + + "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" + let actualBody = String(data: body, encoding: .utf8) + #expect(actualBody == expectedBody) + } else { + Issue.record("Properly-formed URL request was not constructed") + } + } + + @Test + func requestGenerationWithParams() async { + // Set up test + let params: Params = ["test_name": "test_value"] + let multipartData = MultipartData(name: "test_name", + fileData: "test data".data(using: .utf8)!, + fileName: "file.txt", + mimeType: "text/plain") + + // Construct request + var request = baseClient.createRequest(.post, route, params: params) + request.multipartData = [multipartData] + + if let urlRequest = request.buildURLRequest(), + let body = urlRequest.httpBody, + let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { + // Extract boundary from header + #expect(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) + let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") + + // Test correct body construction + let expectedBody = "--\(boundary)\r\nContent-Disposition: " + + "form-data; name=\"test_name\"\r\n\r\ntest_value\r\n--\(boundary)\r\nContent-Disposition: form-data; " + + "name=\"test_name\"; filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest data\r\n--\(boundary)--" + let actualBody = String(data: body, encoding: .utf8) + #expect(actualBody == expectedBody) + } else { + Issue.record("Properly-formed URL request was not constructed") + } + } + + @Test + func requestGenerationWithMultipleFiles() async { + // Set up test + let params: Params = [:] + let multipartData = [ + MultipartData(name: "test_name", + fileData: "test data".data(using: .utf8)!, + fileName: "file.txt", + mimeType: "text/plain"), + MultipartData(name: "second_name", + fileData: "another file".data(using: .utf8)!, + fileName: "file2.txt", + mimeType: "text/plain") + ] + + // Construct request + var request = baseClient.createRequest(.post, route, params: params) + request.multipartData = multipartData + + if let urlRequest = request.buildURLRequest(), + let body = urlRequest.httpBody, + let contentTypeHeader = urlRequest.value(forHTTPHeaderField: "Content-Type") { + // Extract boundary from header + #expect(contentTypeHeader.starts(with: "multipart/form-data; boundary=")) + let boundary = contentTypeHeader.replacingOccurrences(of: "multipart/form-data; boundary=", with: "") + + // Test correct body construction + let expectedBody = "--\(boundary)\r\nContent-Disposition: form-data; name=\"test_name\"; " + + "filename=\"file.txt\"\r\nContent-Type: text/plain\r\n\r\ntest " + + "data\r\n--\(boundary)\r\nContent-Disposition: form-data; name=\"second_name\"; " + + "filename=\"file2.txt\"\r\nContent-Type: text/plain\r\n\r\nanother file\r\n--\(boundary)--" + let actualBody = String(data: body, encoding: .utf8) + #expect(actualBody == expectedBody) + } else { + Issue.record("Properly-formed URL request was not constructed") + } + } +} From fd6ec937605ff2067f2bea7309a15d2b4d4d19b1 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Sun, 6 Oct 2024 14:24:44 -1000 Subject: [PATCH 21/25] Improves example app --- Swift6Arch/Swift6Arch/ContentView.swift | 81 ------------------- Swift6Arch/Swift6Arch/Domain/User.swift | 12 +++ .../Swift6Arch/Domain/UserRepository.swift | 12 +++ .../Swift6Arch/Domain/UserService.swift | 20 +++++ .../JSONAPIUserRepository.swift | 19 +++++ .../Swift6Arch/Infrastructure/UserJSON.swift | 12 +++ .../Presentation/ContentComponent.swift | 23 ++++++ .../Swift6Arch/Presentation/ContentView.swift | 37 +++++++++ .../Presentation/ContentViewModel.swift | 36 +++++++++ Swift6Arch/Swift6Arch/Swift6ArchApp.swift | 43 ---------- 10 files changed, 171 insertions(+), 124 deletions(-) delete mode 100644 Swift6Arch/Swift6Arch/ContentView.swift create mode 100644 Swift6Arch/Swift6Arch/Domain/User.swift create mode 100644 Swift6Arch/Swift6Arch/Domain/UserRepository.swift create mode 100644 Swift6Arch/Swift6Arch/Domain/UserService.swift create mode 100644 Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift create mode 100644 Swift6Arch/Swift6Arch/Infrastructure/UserJSON.swift create mode 100644 Swift6Arch/Swift6Arch/Presentation/ContentComponent.swift create mode 100644 Swift6Arch/Swift6Arch/Presentation/ContentView.swift create mode 100644 Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift diff --git a/Swift6Arch/Swift6Arch/ContentView.swift b/Swift6Arch/Swift6Arch/ContentView.swift deleted file mode 100644 index 0f1a5a7..0000000 --- a/Swift6Arch/Swift6Arch/ContentView.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// ContentView.swift -// Swift6Arch -// -// Created by DURAND SAINT OMER Sacha on 10/2/24. -// - -import SwiftUI - -@MainActor -@Observable -class ContentViewModel { - - var username = "default" - var isLoading = false - - private let userService: UserService - - init(userService: UserService) { - self.userService = userService - } - - func fetchUser() { - Task { @MainActor [userService] in - do { - isLoading = true - let fetchedUser = try await userService.fetchCurrentUser() - username = fetchedUser.name - isLoading = false - } catch { - isLoading = false - print("error") - } - } - } -} - -struct ContentComponent: View { - - @State var viewModel: ContentViewModel - - init(userService: UserService) { - viewModel = ContentViewModel(userService: userService) - } - - var body: some View { - ContentView(username: viewModel.username, - isLoading: viewModel.isLoading, - didTapFetchUsername: viewModel.fetchUser) - } -} - - -struct ContentView: View { - - let username: String - let isLoading: Bool - let didTapFetchUsername: () -> Void - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - - if isLoading { - ProgressView() - } - Text(username) - Button("Fetch username") { - didTapFetchUsername() - } - } - .padding() - } -} - - -#Preview { - ContentView(username: "John", isLoading: false, didTapFetchUsername: {}) -} diff --git a/Swift6Arch/Swift6Arch/Domain/User.swift b/Swift6Arch/Swift6Arch/Domain/User.swift new file mode 100644 index 0000000..80c2b43 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Domain/User.swift @@ -0,0 +1,12 @@ +// +// User.swift +// Swift6Arch +// +// Created by Sacha Durand Saint Omer on 05/10/2024. +// + +import Foundation + +struct User { + let name: String +} diff --git a/Swift6Arch/Swift6Arch/Domain/UserRepository.swift b/Swift6Arch/Swift6Arch/Domain/UserRepository.swift new file mode 100644 index 0000000..34971d9 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Domain/UserRepository.swift @@ -0,0 +1,12 @@ +// +// UserRepository.swift +// Swift6Arch +// +// Created by Sacha Durand Saint Omer on 05/10/2024. +// + +import Foundation + +protocol UserRepository { + func fetchCurrentUser() async throws -> User +} diff --git a/Swift6Arch/Swift6Arch/Domain/UserService.swift b/Swift6Arch/Swift6Arch/Domain/UserService.swift new file mode 100644 index 0000000..be49870 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Domain/UserService.swift @@ -0,0 +1,20 @@ +// +// UserService.swift +// Swift6Arch +// +// Created by Sacha Durand Saint Omer on 05/10/2024. +// + +import Foundation + +struct UserService { + + let userRepository: UserRepository + init(userRepository: UserRepository) { + self.userRepository = userRepository + } + + func fetchCurrentUser() async throws -> User { + return try await userRepository.fetchCurrentUser() + } +} diff --git a/Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift b/Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift new file mode 100644 index 0000000..45e6b59 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift @@ -0,0 +1,19 @@ +// +// JSONAPIUserRepository.swift +// Swift6Arch +// +// Created by Sacha Durand Saint Omer on 05/10/2024. +// + +import Foundation +import Networking + +struct JSONAPIUserRepository: UserRepository { + + let network = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") + + func fetchCurrentUser() async throws -> User { + let userJSON: UserJSON = try await network.get("/users/1") + return User(name: userJSON.name) + } +} diff --git a/Swift6Arch/Swift6Arch/Infrastructure/UserJSON.swift b/Swift6Arch/Swift6Arch/Infrastructure/UserJSON.swift new file mode 100644 index 0000000..387e737 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Infrastructure/UserJSON.swift @@ -0,0 +1,12 @@ +// +// UserJSON.swift +// Swift6Arch +// +// Created by Sacha Durand Saint Omer on 05/10/2024. +// + +import Foundation + +struct UserJSON: Decodable { + let name: String +} diff --git a/Swift6Arch/Swift6Arch/Presentation/ContentComponent.swift b/Swift6Arch/Swift6Arch/Presentation/ContentComponent.swift new file mode 100644 index 0000000..ebe54a5 --- /dev/null +++ b/Swift6Arch/Swift6Arch/Presentation/ContentComponent.swift @@ -0,0 +1,23 @@ +// +// ContentComponent.swift +// Swift6Arch +// +// Created by Sacha Durand Saint Omer on 05/10/2024. +// + +import SwiftUI + +struct ContentComponent: View { + + @State var viewModel: ContentViewModel + + init(userService: UserService) { + viewModel = ContentViewModel(userService: userService) + } + + var body: some View { + ContentView(username: viewModel.username, + isLoading: viewModel.isLoading, + didTapFetchUsername: viewModel.fetchUser) + } +} diff --git a/Swift6Arch/Swift6Arch/Presentation/ContentView.swift b/Swift6Arch/Swift6Arch/Presentation/ContentView.swift new file mode 100644 index 0000000..d8d99ab --- /dev/null +++ b/Swift6Arch/Swift6Arch/Presentation/ContentView.swift @@ -0,0 +1,37 @@ +// +// ContentView.swift +// Swift6Arch +// +// Created by DURAND SAINT OMER Sacha on 10/2/24. +// + +import SwiftUI + +struct ContentView: View { + + let username: String + let isLoading: Bool + let didTapFetchUsername: () -> Void + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + + if isLoading { + ProgressView() + } + Text(username) + Button("Fetch username") { + didTapFetchUsername() + } + } + .padding() + } +} + + +#Preview { + ContentView(username: "John", isLoading: false, didTapFetchUsername: {}) +} diff --git a/Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift b/Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift new file mode 100644 index 0000000..2970d5b --- /dev/null +++ b/Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift @@ -0,0 +1,36 @@ +// +// ContentViewModel.swift +// Swift6Arch +// +// Created by Sacha Durand Saint Omer on 05/10/2024. +// + +import Observation + +@MainActor +@Observable +class ContentViewModel { + + var username = "default" + var isLoading = false + + private let userService: UserService + + init(userService: UserService) { + self.userService = userService + } + + func fetchUser() { + Task { @MainActor [userService] in + do { + isLoading = true + let fetchedUser = try await userService.fetchCurrentUser() + username = fetchedUser.name + isLoading = false + } catch { + isLoading = false + print("error") + } + } + } +} diff --git a/Swift6Arch/Swift6Arch/Swift6ArchApp.swift b/Swift6Arch/Swift6Arch/Swift6ArchApp.swift index 0477a0d..a7f8d68 100644 --- a/Swift6Arch/Swift6Arch/Swift6ArchApp.swift +++ b/Swift6Arch/Swift6Arch/Swift6ArchApp.swift @@ -6,13 +6,10 @@ // import SwiftUI -import Networking -// Presentation @main struct Swift6ArchApp: App { - let userService = UserService(userRepository: JSONAPIUserRepository()) var body: some Scene { WindowGroup { @@ -20,43 +17,3 @@ struct Swift6ArchApp: App { } } } - -// Domain - -class UserService { - - let userRepository: UserRepository - init(userRepository: UserRepository) { - self.userRepository = userRepository - } - - func fetchCurrentUser() async throws -> User { - return try await userRepository.fetchCurrentUser() - } -} - -protocol UserRepository { - func fetchCurrentUser() async throws -> User -} - - -struct User { - let name: String -} - -// Data - -struct JSONAPIUserRepository: UserRepository { - - let network = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") - - func fetchCurrentUser() async throws -> User { - let userJSON: UserJSON = try await network.get("/users/1") - return User(name: userJSON.name) - } -} - - -struct UserJSON: Decodable { - let name: String -} From 09cd1b8444e27e77207948792b70e59b2c5b27ee Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Mon, 7 Oct 2024 07:12:15 -1000 Subject: [PATCH 22/25] WIP - Swift 6 Migration --- Sources/Networking/Calls/NetworkingClient+Data.swift | 4 +++- Sources/Networking/Calls/NetworkingClient+Decodable.swift | 4 ++-- Sources/Networking/Calls/NetworkingClient+JSON.swift | 4 ++-- Sources/Networking/Calls/NetworkingClient+Requests.swift | 2 +- Sources/Networking/Calls/NetworkingClient+Void.swift | 2 +- .../Combine/NetworkingClient+Data+Combine.swift | 4 ++-- Sources/Networking/HTTPMethod.swift | 2 +- Sources/Networking/Multipart/MultipartData.swift | 2 +- Sources/Networking/NetworkingRequest+Execute.swift | 2 +- Sources/Networking/NetworkingRequest.swift | 8 ++++---- 10 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Sources/Networking/Calls/NetworkingClient+Data.swift b/Sources/Networking/Calls/NetworkingClient+Data.swift index b8de514..761f087 100644 --- a/Sources/Networking/Calls/NetworkingClient+Data.swift +++ b/Sources/Networking/Calls/NetworkingClient+Data.swift @@ -17,7 +17,7 @@ public extension NetworkingClient { try await request(.post, route: route, params: params) } - func post(_ route: String, body: Encodable) async throws -> Data { + func post(_ route: String, body: Encodable & Sendable) async throws -> Data { try await execute(request: createRequest(.post, route, encodableBody: body)) } @@ -38,3 +38,5 @@ public extension NetworkingClient { } } + +// Todo execute(request) diff --git a/Sources/Networking/Calls/NetworkingClient+Decodable.swift b/Sources/Networking/Calls/NetworkingClient+Decodable.swift index 5765d66..6f52d31 100644 --- a/Sources/Networking/Calls/NetworkingClient+Decodable.swift +++ b/Sources/Networking/Calls/NetworkingClient+Decodable.swift @@ -33,7 +33,7 @@ public extension NetworkingClient { } func post(_ route: String, - body: Encodable, + body: Encodable & Sendable, keypath: String? = nil ) async throws -> T { let json: JSON = try await post(route, body: body) @@ -79,7 +79,7 @@ public extension NetworkingClient { } func patch(_ route: String, - body: Encodable, + body: Encodable & Sendable, keypath: String? = nil ) async throws -> T { let json: JSON = try await patch(route, body: body) diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift index 93f5b28..a2a7750 100644 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ b/Sources/Networking/Calls/NetworkingClient+JSON.swift @@ -25,7 +25,7 @@ public extension NetworkingClient { return JSON(jsonObject: json) } - func post(_ route: String, body: Encodable) async throws -> JSON { + func post(_ route: String, body: Encodable & Sendable) async throws -> JSON { let req = createRequest(.post, route, encodableBody: body) let data = try await execute(request: req) let json = try JSONSerialization.jsonObject(with: data, options: []) @@ -48,7 +48,7 @@ public extension NetworkingClient { return JSON(jsonObject: try await patch(route, params: params)) } - func patch(_ route: String, body: Encodable) async throws -> JSON { + func patch(_ route: String, body: Encodable & Sendable) async throws -> JSON { let req = createRequest(.patch, route, encodableBody: body) let data = try await execute(request: req) let json = try JSONSerialization.jsonObject(with: data, options: []) diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index 779c1e1..3bf6401 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -48,7 +48,7 @@ public extension NetworkingClient { internal func createRequest(_ httpMethod: HTTPMethod, _ route: String, params: Params = Params(), - encodableBody: Encodable? = nil + encodableBody: (Encodable & Sendable)? = nil ) -> NetworkingRequest { let req = NetworkingRequest( method: httpMethod, diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift index c0b9692..2072fba 100644 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ b/Sources/Networking/Calls/NetworkingClient+Void.swift @@ -17,7 +17,7 @@ public extension NetworkingClient { _ = try await request(.post, route: route, params: params) } - func post(_ route: String, body: Encodable) async throws { + func post(_ route: String, body: Encodable & Sendable) async throws { let req = createRequest(.post, route, encodableBody: body) _ = try await execute(request: req) } diff --git a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift index 107ee42..88c4f36 100644 --- a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift +++ b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift @@ -18,7 +18,7 @@ public extension NetworkingClient { request(.post, route: route, params: params) } - func post(_ route: String, body: Encodable) -> AnyPublisher { + func post(_ route: String, body: Encodable & Sendable) -> AnyPublisher { publisher(request: createRequest(.post, route, encodableBody: body)) } @@ -30,7 +30,7 @@ public extension NetworkingClient { request(.patch, route: route, params: params) } - func patch(_ route: String, body: Encodable) -> AnyPublisher { + func patch(_ route: String, body: Encodable & Sendable) -> AnyPublisher { publisher(request: createRequest(.patch, route, encodableBody: body)) } diff --git a/Sources/Networking/HTTPMethod.swift b/Sources/Networking/HTTPMethod.swift index 453abce..ad2d2b0 100644 --- a/Sources/Networking/HTTPMethod.swift +++ b/Sources/Networking/HTTPMethod.swift @@ -7,7 +7,7 @@ import Foundation -public enum HTTPMethod: String { +public enum HTTPMethod: String, Sendable { case get = "GET" case put = "PUT" case patch = "PATCH" diff --git a/Sources/Networking/Multipart/MultipartData.swift b/Sources/Networking/Multipart/MultipartData.swift index e8e595a..2c5e8f4 100644 --- a/Sources/Networking/Multipart/MultipartData.swift +++ b/Sources/Networking/Multipart/MultipartData.swift @@ -7,7 +7,7 @@ import Foundation -public struct MultipartData { +public struct MultipartData: Sendable { let name: String let fileData: Data let fileName: String diff --git a/Sources/Networking/NetworkingRequest+Execute.swift b/Sources/Networking/NetworkingRequest+Execute.swift index 25a6b90..4e15538 100644 --- a/Sources/Networking/NetworkingRequest+Execute.swift +++ b/Sources/Networking/NetworkingRequest+Execute.swift @@ -9,7 +9,7 @@ import Foundation extension NetworkingClient { - func execute(request: NetworkingRequest) async throws -> Data { + public func execute(request: NetworkingRequest) async throws -> Data { guard let urlRequest = request.buildURLRequest() else { throw NetworkingError.unableToParseRequest } diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index f03246e..c4f8b3a 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -7,12 +7,12 @@ import Foundation -public struct NetworkingRequest { +public struct NetworkingRequest: Sendable { let method: HTTPMethod let url: String let parameterEncoding: ParameterEncoding - let params: Params - let encodableBody: Encodable? + public var params: Params + public var encodableBody: (Encodable & Sendable)? let headers: [String: String] var multipartData: [MultipartData]? let timeout: TimeInterval? @@ -20,7 +20,7 @@ public struct NetworkingRequest { } -public enum ParameterEncoding { +public enum ParameterEncoding: Sendable { case urlEncoded case json } From 9b510f23b5d4987e0567371bc6f2a63a316faf60 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Tue, 1 Jul 2025 12:19:29 +0200 Subject: [PATCH 23/25] Adds beforeRequest & mapError hooks --- Sources/Networking/Calls/NetworkingClient+Data.swift | 11 +++++++---- Sources/Networking/NetworkingClient.swift | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/Networking/Calls/NetworkingClient+Data.swift b/Sources/Networking/Calls/NetworkingClient+Data.swift index 761f087..7763963 100644 --- a/Sources/Networking/Calls/NetworkingClient+Data.swift +++ b/Sources/Networking/Calls/NetworkingClient+Data.swift @@ -34,9 +34,12 @@ public extension NetworkingClient { } func request(_ httpMethod: HTTPMethod, route: String, params: Params = Params()) async throws -> Data { - try await execute(request: createRequest(httpMethod, route, params: params)) + try await beforeRequest() + let request = createRequest(httpMethod, route, params: params) + do { + return try await execute(request: request) + } catch { + throw mapError(error) + } } - } - -// Todo execute(request) diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index d8c6797..568e832 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -33,6 +33,8 @@ public class NetworkingClient { public var sessionConfiguration = URLSessionConfiguration.default // public var requestRetrier: NetworkRequestRetrier? public var jsonDecoderFactory: (() -> JSONDecoder)? + public var beforeRequest: () async throws -> Void = {} + public var mapError: (Error) -> Error = { $0 } let sessionDelegate = NetworkingClientURLSessionDelegate() /** From e74b9e63db31278f27f634bc50b37095806c3174 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 2 Jul 2025 11:33:39 +0200 Subject: [PATCH 24/25] Make NetworkingClient thread safe via actor (aka Sendable) --- .../Calls/NetworkingClient+Data.swift | 8 +- .../Calls/NetworkingClient+JSON.swift | 6 +- .../Calls/NetworkingClient+Requests.swift | 20 +- .../Calls/NetworkingClient+Void.swift | 3 +- .../NetworkingClient+Data+Combine.swift | 4 +- .../Combine/NetworkingRequest+Publisher.swift | 8 +- .../Combine/NetworkingService+Combine.swift | 446 ++++++++-------- .../Networking/Logging/NetworkingLogger.swift | 16 +- Sources/Networking/NetworkingClient.swift | 29 +- .../NetworkingRequest+Execute.swift | 4 +- Sources/Networking/NetworkingService.swift | 12 +- .../Swift6Arch/Domain/UserService.swift | 2 +- .../Presentation/ContentViewModel.swift | 2 +- .../Combine/DeleteRequestTests+Combine.swift | 320 ++++++------ .../Combine/GetRequestTests+Combine.swift | 490 +++++++++--------- .../Combine/PatchRequestTests+Combine.swift | 320 ++++++------ .../Combine/PostRequestTests+Combine.swift | 352 ++++++------- .../Combine/PutRequestTests+Combine.swift | 320 ++++++------ .../NetworkingTests/DeleteRequestTests.swift | 4 +- Tests/NetworkingTests/GetRequestTests.swift | 4 +- .../MultipartRequestTests.swift | 6 +- Tests/NetworkingTests/PatchRequestTests.swift | 8 +- Tests/NetworkingTests/PostRequestTests.swift | 4 +- Tests/NetworkingTests/PutRequestTests.swift | 4 +- 24 files changed, 1193 insertions(+), 1199 deletions(-) diff --git a/Sources/Networking/Calls/NetworkingClient+Data.swift b/Sources/Networking/Calls/NetworkingClient+Data.swift index 7763963..5154f97 100644 --- a/Sources/Networking/Calls/NetworkingClient+Data.swift +++ b/Sources/Networking/Calls/NetworkingClient+Data.swift @@ -18,7 +18,7 @@ public extension NetworkingClient { } func post(_ route: String, body: Encodable & Sendable) async throws -> Data { - try await execute(request: createRequest(.post, route, encodableBody: body)) + try await request(.post, route: route, body: body) } func put(_ route: String, params: Params = Params()) async throws -> Data { @@ -33,9 +33,9 @@ public extension NetworkingClient { try await request(.delete, route: route, params: params) } - func request(_ httpMethod: HTTPMethod, route: String, params: Params = Params()) async throws -> Data { - try await beforeRequest() - let request = createRequest(httpMethod, route, params: params) + func request(_ httpMethod: HTTPMethod, route: String, params: Params = Params(), body: (Encodable & Sendable)? = nil) async throws -> Data { + try await beforeRequest(self) + let request = createRequest(httpMethod, route, params: params, body: body) do { return try await execute(request: request) } catch { diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift index a2a7750..032c8a3 100644 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ b/Sources/Networking/Calls/NetworkingClient+JSON.swift @@ -26,8 +26,7 @@ public extension NetworkingClient { } func post(_ route: String, body: Encodable & Sendable) async throws -> JSON { - let req = createRequest(.post, route, encodableBody: body) - let data = try await execute(request: req) + let data = try await request(.post, route: route, body: body) let json = try JSONSerialization.jsonObject(with: data, options: []) return JSON(jsonObject: json) } @@ -49,8 +48,7 @@ public extension NetworkingClient { } func patch(_ route: String, body: Encodable & Sendable) async throws -> JSON { - let req = createRequest(.patch, route, encodableBody: body) - let data = try await execute(request: req) + let data = try await request(.patch, route: route, body: body) let json = try JSONSerialization.jsonObject(with: data, options: []) return JSON(jsonObject: json) } diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index 3bf6401..3e72c92 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -28,34 +28,18 @@ public extension NetworkingClient { func deleteRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { createRequest(.delete, route, params: params) } - - internal func createRequest(_ httpMethod: HTTPMethod, - _ route: String, - params: Params = Params() - ) -> NetworkingRequest { - let req = NetworkingRequest( - method: httpMethod, - url: baseURL + route, - parameterEncoding: parameterEncoding, - params: params, - encodableBody: nil, - headers: headers, - multipartData: nil, - timeout: timeout) - return req - } internal func createRequest(_ httpMethod: HTTPMethod, _ route: String, params: Params = Params(), - encodableBody: (Encodable & Sendable)? = nil + body: (Encodable & Sendable)? = nil ) -> NetworkingRequest { let req = NetworkingRequest( method: httpMethod, url: baseURL + route, parameterEncoding: parameterEncoding, params: params, - encodableBody: encodableBody, + encodableBody: body, headers: headers, multipartData: nil, timeout: timeout) diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift index 2072fba..402de17 100644 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ b/Sources/Networking/Calls/NetworkingClient+Void.swift @@ -18,8 +18,7 @@ public extension NetworkingClient { } func post(_ route: String, body: Encodable & Sendable) async throws { - let req = createRequest(.post, route, encodableBody: body) - _ = try await execute(request: req) + _ = try await request(.post, route: route, body: body) } func put(_ route: String, params: Params = Params()) async throws { diff --git a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift index 88c4f36..d8677cb 100644 --- a/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift +++ b/Sources/Networking/Combine/NetworkingClient+Data+Combine.swift @@ -19,7 +19,7 @@ public extension NetworkingClient { } func post(_ route: String, body: Encodable & Sendable) -> AnyPublisher { - publisher(request: createRequest(.post, route, encodableBody: body)) + publisher(request: createRequest(.post, route, body: body)) } func put(_ route: String, params: Params = Params()) -> AnyPublisher { @@ -31,7 +31,7 @@ public extension NetworkingClient { } func patch(_ route: String, body: Encodable & Sendable) -> AnyPublisher { - publisher(request: createRequest(.patch, route, encodableBody: body)) + publisher(request: createRequest(.patch, route, body: body)) } func delete(_ route: String, params: Params = Params()) -> AnyPublisher { diff --git a/Sources/Networking/Combine/NetworkingRequest+Publisher.swift b/Sources/Networking/Combine/NetworkingRequest+Publisher.swift index 46255f4..986368c 100644 --- a/Sources/Networking/Combine/NetworkingRequest+Publisher.swift +++ b/Sources/Networking/Combine/NetworkingRequest+Publisher.swift @@ -17,12 +17,12 @@ extension NetworkingClient { return Fail(error: NetworkingError.unableToParseRequest as Error) .eraseToAnyPublisher() } - logger.log(request: urlRequest) + logger.log(request: urlRequest, level: logLevel) let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) let callPublisher: AnyPublisher<(Data?, Progress), Error> = urlSession.dataTaskPublisher(for: urlRequest) .tryMap { (data: Data, response: URLResponse) -> Data in - self.logger.log(response: response, data: data) + self.logger.log(response: response, data: data, level: self.logLevel) if let httpURLResponse = response as? HTTPURLResponse { if !(200...299 ~= httpURLResponse.statusCode) { var error = NetworkingError(errorCode: httpURLResponse.statusCode) @@ -62,12 +62,12 @@ extension NetworkingClient { return Fail(error: NetworkingError.unableToParseRequest as Error) .eraseToAnyPublisher() } - logger.log(request: urlRequest) + logger.log(request: urlRequest, level: logLevel) let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) return urlSession.dataTaskPublisher(for: urlRequest) .tryMap { (data: Data, response: URLResponse) -> Data in - self.logger.log(response: response, data: data) + self.logger.log(response: response, data: data, level: self.logLevel) if let httpURLResponse = response as? HTTPURLResponse { if !(200...299 ~= httpURLResponse.statusCode) { var error = NetworkingError(errorCode: httpURLResponse.statusCode) diff --git a/Sources/Networking/Combine/NetworkingService+Combine.swift b/Sources/Networking/Combine/NetworkingService+Combine.swift index 0894f07..bedde3a 100644 --- a/Sources/Networking/Combine/NetworkingService+Combine.swift +++ b/Sources/Networking/Combine/NetworkingService+Combine.swift @@ -1,227 +1,227 @@ +//// +//// NetworkingService.swift +//// +//// +//// Created by Sacha on 13/03/2020. +//// // -// NetworkingService.swift +//import Foundation +//import Combine // +//// Sugar, just forward calls to underlying network client // -// Created by Sacha on 13/03/2020. +//public extension NetworkingService { +// +// // Data +// +// func get(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable & Sendable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func patch(_ route: String, body: Encodable) -> AnyPublisher { +// network.patch(route, body: body) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // Void +// +// func get(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // JSON +// +// func get(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.get(route, params: params) +// } +// +// func post(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.post(route, params: params) +// } +// +// func post(_ route: String, body: Encodable) -> AnyPublisher { +// network.post(route, body: body) +// } +// +// func put(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.put(route, params: params) +// } +// +// func patch(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.patch(route, params: params) +// } +// +// func delete(_ route: String, params: Params = Params()) -> AnyPublisher { +// network.delete(route, params: params) +// } +// +// // Decodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.delete(route, params: params, keypath: keypath) +// } +// +// // Array Decodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher where T: Collection { +// network.delete(route, params: params, keypath: keypath) +// } +// +// // NetworkingJSONDecodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher { +// network.delete(route, params: params, keypath: keypath) +// } +// +// +// +// // Array NetworkingJSONDecodable +// +// func get(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.get(route, params: params, keypath: keypath) +// } +// +// func post(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.post(route, params: params, keypath: keypath) +// } +// +// func put(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.put(route, params: params, keypath: keypath) +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.patch(route, params: params, keypath: keypath) +// } +// +// func delete(_ route: String, +// params: Params = Params(), +// keypath: String? = nil) -> AnyPublisher<[T], Error> { +// network.delete(route, params: params, keypath: keypath) +// } +//} // - -import Foundation -import Combine - -// Sugar, just forward calls to underlying network client - -public extension NetworkingService { - - // Data - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable & Sendable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - network.patch(route, body: body) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // Void - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // JSON - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.delete(route, params: params, keypath: keypath) - } - - // Array Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.delete(route, params: params, keypath: keypath) - } - - // NetworkingJSONDecodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.delete(route, params: params, keypath: keypath) - } - - - - // Array NetworkingJSONDecodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.delete(route, params: params, keypath: keypath) - } -} - diff --git a/Sources/Networking/Logging/NetworkingLogger.swift b/Sources/Networking/Logging/NetworkingLogger.swift index 253707a..27cb64e 100644 --- a/Sources/Networking/Logging/NetworkingLogger.swift +++ b/Sources/Networking/Logging/NetworkingLogger.swift @@ -7,12 +7,10 @@ import Foundation -class NetworkingLogger { +struct NetworkingLogger { - var logLevel = NetworkingLogLevel.off - - func log(request: URLRequest) { - guard logLevel != .off else { + func log(request: URLRequest, level: NetworkingLogLevel) { + guard level != .off else { return } if let method = request.httpMethod, @@ -22,19 +20,19 @@ class NetworkingLogger { logBody(request) } - if logLevel == .debug { + if level == .debug { logCurl(request) } } - func log(response: URLResponse, data: Data) { - guard logLevel != .off else { + func log(response: URLResponse, data: Data, level: NetworkingLogLevel) { + guard level != .off else { return } if let response = response as? HTTPURLResponse { logStatusCodeAndURL(response) } - if logLevel == .debug { + if level == .debug { print(String(decoding: data, as: UTF8.self)) } } diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index 568e832..879e721 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -18,7 +18,7 @@ actor NetworkingClientURLSessionDelegate: NSObject, URLSessionDelegate { // public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? -public class NetworkingClient { +public actor NetworkingClient { /** Instead of using the same keypath for every call eg: "collection", this enables to use a default keypath for parsing collections. @@ -33,7 +33,7 @@ public class NetworkingClient { public var sessionConfiguration = URLSessionConfiguration.default // public var requestRetrier: NetworkRequestRetrier? public var jsonDecoderFactory: (() -> JSONDecoder)? - public var beforeRequest: () async throws -> Void = {} + public var beforeRequest: (NetworkingClient) async throws -> Void = { _ in } public var mapError: (Error) -> Error = { $0 } let sessionDelegate = NetworkingClientURLSessionDelegate() @@ -42,10 +42,7 @@ public class NetworkingClient { Values Available are .None, Calls and CallsAndResponses. Default is None */ - public var logLevel: NetworkingLogLevel { - get { return logger.logLevel } - set { logger.logLevel = newValue } - } + public var logLevel = NetworkingLogLevel.off internal let logger = NetworkingLogger() @@ -53,7 +50,25 @@ public class NetworkingClient { self.baseURL = baseURL self.timeout = timeout } - + + public init( + baseURL: String, + headers: [String: String] = [String: String](), + parameterEncoding: ParameterEncoding = .urlEncoded, + logLevel: NetworkingLogLevel = .off, + jsonDecoderFactory: (() -> JSONDecoder)? = nil, + beforeRequest: @escaping (NetworkingClient) async throws -> Void = { _ in }, + mapError: @escaping (Error) -> Error = { $0 }) { + self.baseURL = baseURL + self.headers = headers + self.parameterEncoding = parameterEncoding + self.logLevel = logLevel + self.timeout = nil + self.jsonDecoderFactory = jsonDecoderFactory + self.beforeRequest = beforeRequest + self.mapError = mapError + } + public func toModel(_ json: JSON, keypath: String? = nil) throws -> T { do { let data = resourceData(from: json, keypath: keypath) diff --git a/Sources/Networking/NetworkingRequest+Execute.swift b/Sources/Networking/NetworkingRequest+Execute.swift index 4e15538..f6cfc84 100644 --- a/Sources/Networking/NetworkingRequest+Execute.swift +++ b/Sources/Networking/NetworkingRequest+Execute.swift @@ -13,10 +13,10 @@ extension NetworkingClient { guard let urlRequest = request.buildURLRequest() else { throw NetworkingError.unableToParseRequest } - logger.log(request: urlRequest) + logger.log(request: urlRequest, level: logLevel) let urlSession = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) let (data, response) = try await urlSession.data(for: urlRequest) - logger.log(response: response, data: data) + logger.log(response: response, data: data, level: logLevel) if let httpURLResponse = response as? HTTPURLResponse, !(200...299 ~= httpURLResponse.statusCode) { var error = NetworkingError(errorCode: httpURLResponse.statusCode) if let json = try? JSONSerialization.jsonObject(with: data, options: []) { diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index 740220c..3555102 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -70,9 +70,9 @@ public extension NetworkingService { // JSON - func get(_ route: String, params: Params = Params()) async throws -> Any { - try await network.get(route, params: params) - } +// func get(_ route: String, params: Params = Params()) async throws -> Any { +// try await network.get(route, params: params) +// } func get(_ route: String, params: Params = Params()) async throws -> JSON { try await network.get(route, params: params) @@ -90,9 +90,9 @@ public extension NetworkingService { try await network.put(route, params: params) } - func patch(_ route: String, params: Params = Params()) async throws -> Any { - try await network.patch(route, params: params) - } +// func patch(_ route: String, params: Params = Params()) async throws -> Any { +// try await network.patch(route, params: params) +// } func patch(_ route: String, params: Params = Params()) async throws -> JSON { try await network.patch(route, params: params) diff --git a/Swift6Arch/Swift6Arch/Domain/UserService.swift b/Swift6Arch/Swift6Arch/Domain/UserService.swift index be49870..7132646 100644 --- a/Swift6Arch/Swift6Arch/Domain/UserService.swift +++ b/Swift6Arch/Swift6Arch/Domain/UserService.swift @@ -7,7 +7,7 @@ import Foundation -struct UserService { +actor UserService { let userRepository: UserRepository init(userRepository: UserRepository) { diff --git a/Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift b/Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift index 2970d5b..09c6fea 100644 --- a/Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift +++ b/Swift6Arch/Swift6Arch/Presentation/ContentViewModel.swift @@ -21,7 +21,7 @@ class ContentViewModel { } func fetchUser() { - Task { @MainActor [userService] in + Task { do { isLoading = true let fetchedUser = try await userService.fetchCurrentUser() diff --git a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift index 472ab9f..5bf4476 100644 --- a/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/DeleteRequestTests+Combine.swift @@ -1,163 +1,163 @@ +//// +//// DeleteRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// DeleteRequestTests.swift -// +//import Testing +//import Combine +//import Foundation +//import Networking // -// Created by Sacha DSO on 12/04/2022. +//@Suite(.serialized) +//class DeleteRequestCombineTests { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() // - -import Testing -import Combine -import Foundation -import Networking - -@Suite(.serialized) -class DeleteRequestCombineTests { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - @Test - func DELETEVoidWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - - let _: Void = await testHelper(network.delete("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } - - @Test - func DELETEDataWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = await testHelper(network.delete("/users")) - #expect(data != nil) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } - - @Test - func DELETEJSONWorks() async { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: JSON = await testHelper(network.delete("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - #expect(data == expectedResponseData) - } - // Todo put back Sendable version - - @Test - func testDELETENetworkingJSONDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let post: Post = await testHelper(network.delete("/posts/1")) - #expect(post.title == "Hello") - #expect(post.content == "World") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") - } - - @Test - func testDELETEDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON: UserJSON = await testHelper(network.delete("/users/1")) - #expect(userJSON.firstname == "John") - #expect(userJSON.lastname == "Doe") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") - } - - @Test - func testDELETEArrayOfDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let userJSON: [UserJSON] = await testHelper(network.delete("/users")) - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } - - @Test - func testDELETEArrayOfDecodableWithKeypathWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let userJSON: [UserJSON] = await testHelper(network.delete("/users", keypath: "users")) - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } - - func testHelper(_ publisher: AnyPublisher) async -> T { - return await withCheckedContinuation { continuation in - publisher.sink { completion in - switch completion { - case .failure(_): - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { x in - continuation.resume(returning: x) - } - .store(in: &cancellables) - } - } -} +// init() { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// @Test +// func DELETEVoidWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// +// let _: Void = await testHelper(network.delete("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +// +// @Test +// func DELETEDataWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = await testHelper(network.delete("/users")) +// #expect(data != nil) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +// +// @Test +// func DELETEJSONWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: JSON = await testHelper(network.delete("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// #expect(data == expectedResponseData) +// } +// // Todo put back Sendable version +// +// @Test +// func testDELETENetworkingJSONDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let post: Post = await testHelper(network.delete("/posts/1")) +// #expect(post.title == "Hello") +// #expect(post.content == "World") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") +// } +// +// @Test +// func testDELETEDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON: UserJSON = await testHelper(network.delete("/users/1")) +// #expect(userJSON.firstname == "John") +// #expect(userJSON.lastname == "Doe") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") +// } +// +// @Test +// func testDELETEArrayOfDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let userJSON: [UserJSON] = await testHelper(network.delete("/users")) +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +// +// @Test +// func testDELETEArrayOfDecodableWithKeypathWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let userJSON: [UserJSON] = await testHelper(network.delete("/users", keypath: "users")) +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "DELETE") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +// +// func testHelper(_ publisher: AnyPublisher) async -> T { +// return await withCheckedContinuation { continuation in +// publisher.sink { completion in +// switch completion { +// case .failure(_): +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { x in +// continuation.resume(returning: x) +// } +// .store(in: &cancellables) +// } +// } +//} diff --git a/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift index 61fcef6..f63033a 100644 --- a/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/GetRequestTests+Combine.swift @@ -1,248 +1,248 @@ +//// +//// GetRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// GetRequestTests.swift -// +//import Testing +//import Foundation +//import Combine // -// Created by Sacha DSO on 12/04/2022. +//@testable +//import Networking +// +//@Suite(.serialized) +//class GetRequestCombineTests { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// init() { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// @Test +// func GETVoidPublisher() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// +// let result = await withCheckedContinuation { continuation in +// network.get("/users").sink { completion in +// switch completion { +// case .failure(_): +// Issue.record("Call failed") +// case .finished: +// continuation.resume(returning: "done") +// } +// } receiveValue: { () in +// +// } +// .store(in: &cancellables) +// } +// #expect(result == "done") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// +// } +// +// @Test +// func GETDataPublisher() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let result = await withCheckedContinuation { continuation in +// network.get("/users").sink { completion in +// switch completion { +// case .failure: +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { (data: Data) in +// continuation.resume(returning: data) +// } +// .store(in: &cancellables) +// } +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(result == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// +// } +// +// func foo() -> AnyPublisher { +// return network.get("/users") +// } +// +// @Test +// func GETJSONPublisher() async { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// +// let result = await withCheckedContinuation { continuation in +// network.get("/users").sink { completion in +// switch completion { +// case .failure: +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { (json: Sendable) in +// continuation.resume(returning: json) +// } +// .store(in: &cancellables) +// } +// +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: result, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// +// #expect(data == expectedResponseData) +// } +// +// @Test +// func GETNetworkingJSONDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let post = await withCheckedContinuation { continuation in +// network.get("/posts/1") +// .sink { completion in +// switch completion { +// case .failure: +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { (post: Post) in +// continuation.resume(returning: post) +// } +// .store(in: &cancellables) +// } +// #expect(post.title == "Hello") +// #expect(post.content == "World") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") +// } +// +// @Test +// func GETDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON = await withCheckedContinuation { continuation in +// network.get("/users/1") +// .sink { completion in +// switch completion { +// case .failure: +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { (userJSON: UserJSON) in +// continuation.resume(returning: userJSON) +// } +// .store(in: &cancellables) +// } +// #expect(userJSON.firstname == "John") +// #expect(userJSON.lastname == "Doe") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") +// } +// +// @Test +// func GETArrayOfDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let userJSON = await withCheckedContinuation { continuation in +// network.get("/users") +// .sink { completion in +// switch completion { +// case .failure: +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// continuation.resume(returning: userJSON) +// } +// .store(in: &cancellables) +// } +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// +// } +// +// @Test +// func GETArrayOfDecodableWithKeypathWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let userJSON = await withCheckedContinuation { continuation in +// network.get("/users", keypath: "users") +// .sink { completion in +// switch completion { +// case .failure: +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { (userJSON: [UserJSON]) in +// continuation.resume(returning: userJSON) +// } +// .store(in: &cancellables) +// } +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +//} // - -import Testing -import Foundation -import Combine - -@testable -import Networking - -@Suite(.serialized) -class GetRequestCombineTests { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - @Test - func GETVoidPublisher() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - - let result = await withCheckedContinuation { continuation in - network.get("/users").sink { completion in - switch completion { - case .failure(_): - Issue.record("Call failed") - case .finished: - continuation.resume(returning: "done") - } - } receiveValue: { () in - - } - .store(in: &cancellables) - } - #expect(result == "done") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - - } - - @Test - func GETDataPublisher() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let result = await withCheckedContinuation { continuation in - network.get("/users").sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (data: Data) in - continuation.resume(returning: data) - } - .store(in: &cancellables) - } - #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(result == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - - } - - func foo() -> AnyPublisher { - return network.get("/users") - } - - @Test - func GETJSONPublisher() async { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - - let result = await withCheckedContinuation { continuation in - network.get("/users").sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (json: Sendable) in - continuation.resume(returning: json) - } - .store(in: &cancellables) - } - - #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: result, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - - #expect(data == expectedResponseData) - } - - @Test - func GETNetworkingJSONDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let post = await withCheckedContinuation { continuation in - network.get("/posts/1") - .sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (post: Post) in - continuation.resume(returning: post) - } - .store(in: &cancellables) - } - #expect(post.title == "Hello") - #expect(post.content == "World") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") - } - - @Test - func GETDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON = await withCheckedContinuation { continuation in - network.get("/users/1") - .sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (userJSON: UserJSON) in - continuation.resume(returning: userJSON) - } - .store(in: &cancellables) - } - #expect(userJSON.firstname == "John") - #expect(userJSON.lastname == "Doe") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") - } - - @Test - func GETArrayOfDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let userJSON = await withCheckedContinuation { continuation in - network.get("/users") - .sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (userJSON: [UserJSON]) in - continuation.resume(returning: userJSON) - } - .store(in: &cancellables) - } - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - - } - - @Test - func GETArrayOfDecodableWithKeypathWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let userJSON = await withCheckedContinuation { continuation in - network.get("/users", keypath: "users") - .sink { completion in - switch completion { - case .failure: - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { (userJSON: [UserJSON]) in - continuation.resume(returning: userJSON) - } - .store(in: &cancellables) - } - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - #expect(MockingURLProtocol.currentRequest?.httpMethod == "GET") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } -} - diff --git a/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift index e5e6b9d..10e96b7 100644 --- a/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PatchRequestTests+Combine.swift @@ -1,163 +1,163 @@ +//// +//// PatchRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// PatchRequestTests.swift -// +//import Foundation +//import Testing +//import Combine // -// Created by Sacha DSO on 12/04/2022. +//@testable +//import Networking // - -import Foundation -import Testing -import Combine - -@testable -import Networking - -@Suite(.serialized) -class PatchRequestCombineTests { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - @Test - func PATCHVoidWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _: Void = await testHelper(network.patch("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } - - @Test - func PATCHDataWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = await testHelper(network.patch("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - @Test - func PATCHJSONWorks() async { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: JSON = await testHelper(network.patch("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - #expect(data == expectedResponseData) - } - - @Test - func testPATCHNetworkingJSONDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let post: Post = await testHelper(network.patch("/posts/1")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") - #expect(post.title == "Hello") - #expect(post.content == "World") - } - - @Test - func PATCHDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON: UserJSON = await testHelper(network.patch("/users/1")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") - #expect(userJSON.firstname == "John") - #expect(userJSON.lastname == "Doe") - } - - @Test - func PATCHArrayOfDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let userJSON: [UserJSON] = await testHelper(network.patch("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - } - - @Test - func testPATCHArrayOfDecodableWithKeypathWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let userJSON: [UserJSON] = await testHelper(network.patch("/users", keypath: "users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - } - - func testHelper(_ publisher: AnyPublisher) async -> T { - return await withCheckedContinuation { continuation in - publisher.sink { completion in - switch completion { - case .failure(_): - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { x in - continuation.resume(returning: x) - } - .store(in: &cancellables) - } - } -} +//@Suite(.serialized) +//class PatchRequestCombineTests { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// init() { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// @Test +// func PATCHVoidWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _: Void = await testHelper(network.patch("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +// +// @Test +// func PATCHDataWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = await testHelper(network.patch("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// @Test +// func PATCHJSONWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: JSON = await testHelper(network.patch("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// #expect(data == expectedResponseData) +// } +// +// @Test +// func testPATCHNetworkingJSONDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let post: Post = await testHelper(network.patch("/posts/1")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") +// #expect(post.title == "Hello") +// #expect(post.content == "World") +// } +// +// @Test +// func PATCHDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON: UserJSON = await testHelper(network.patch("/users/1")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") +// #expect(userJSON.firstname == "John") +// #expect(userJSON.lastname == "Doe") +// } +// +// @Test +// func PATCHArrayOfDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let userJSON: [UserJSON] = await testHelper(network.patch("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// } +// +// @Test +// func testPATCHArrayOfDecodableWithKeypathWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let userJSON: [UserJSON] = await testHelper(network.patch("/users", keypath: "users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// } +// +// func testHelper(_ publisher: AnyPublisher) async -> T { +// return await withCheckedContinuation { continuation in +// publisher.sink { completion in +// switch completion { +// case .failure(_): +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { x in +// continuation.resume(returning: x) +// } +// .store(in: &cancellables) +// } +// } +//} diff --git a/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift index fd8b9eb..fca8b0b 100644 --- a/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PostRequestTests+Combine.swift @@ -1,179 +1,179 @@ +//// +//// PostRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// PostRequestTests.swift -// +//import Foundation +//import Testing +//import Combine // -// Created by Sacha DSO on 12/04/2022. +//@testable +//import Networking // - -import Foundation -import Testing -import Combine - -@testable -import Networking - -@Suite(.serialized) -class PostRequestCombineTests { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - @Test - func POSTVoidWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _: Void = await testHelper(network.post("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } - - @Test - func POSTDataWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = await testHelper(network.post("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - @Test - func POSTJSONWorks() async { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: JSON = await testHelper(network.post("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - #expect(data == expectedResponseData) - } - - @Test - func POSTNetworkingJSONDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let post: Post = await testHelper(network.post("/posts/1")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") - #expect(post.title == "Hello") - #expect(post.content == "World") - } - - @Test - func POSTDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON: UserJSON = await testHelper(network.post("/users/1")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") - #expect(userJSON.firstname == "John") - #expect(userJSON.lastname == "Doe") - } - - @Test - func POSTArrayOfDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let userJSON: [UserJSON] = await testHelper(network.post("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - } - - @Test - func POSTArrayOfDecodableWithKeypathWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let userJSON: [UserJSON] = await testHelper(network.post("/users", keypath: "users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - } - - @Test - func POSTDataEncodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let creds = Credentials(username: "Alan", password: "Turing") - let data: Data = await testHelper(network.post("/users", body: creds)) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() - #expect(body?["username"] as? String == "Alan") - #expect(body?["password"] as? String == "Turing") - #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - func testHelper(_ publisher: AnyPublisher) async -> T { - return await withCheckedContinuation { continuation in - publisher.sink { completion in - switch completion { - case .failure(_): - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { x in - continuation.resume(returning: x) - } - .store(in: &cancellables) - } - } -} +//@Suite(.serialized) +//class PostRequestCombineTests { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() +// +// init() { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// @Test +// func POSTVoidWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _: Void = await testHelper(network.post("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +// +// @Test +// func POSTDataWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = await testHelper(network.post("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// @Test +// func POSTJSONWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: JSON = await testHelper(network.post("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// #expect(data == expectedResponseData) +// } +// +// @Test +// func POSTNetworkingJSONDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let post: Post = await testHelper(network.post("/posts/1")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") +// #expect(post.title == "Hello") +// #expect(post.content == "World") +// } +// +// @Test +// func POSTDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON: UserJSON = await testHelper(network.post("/users/1")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") +// #expect(userJSON.firstname == "John") +// #expect(userJSON.lastname == "Doe") +// } +// +// @Test +// func POSTArrayOfDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let userJSON: [UserJSON] = await testHelper(network.post("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// } +// +// @Test +// func POSTArrayOfDecodableWithKeypathWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let userJSON: [UserJSON] = await testHelper(network.post("/users", keypath: "users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// } +// +// @Test +// func POSTDataEncodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let creds = Credentials(username: "Alan", password: "Turing") +// let data: Data = await testHelper(network.post("/users", body: creds)) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "POST") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() +// #expect(body?["username"] as? String == "Alan") +// #expect(body?["password"] as? String == "Turing") +// #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// func testHelper(_ publisher: AnyPublisher) async -> T { +// return await withCheckedContinuation { continuation in +// publisher.sink { completion in +// switch completion { +// case .failure(_): +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { x in +// continuation.resume(returning: x) +// } +// .store(in: &cancellables) +// } +// } +//} diff --git a/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift index 51293c8..2f709c1 100644 --- a/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift +++ b/Tests/NetworkingTests/Combine/PutRequestTests+Combine.swift @@ -1,164 +1,164 @@ +//// +//// PutRequestTests.swift +//// +//// +//// Created by Sacha DSO on 12/04/2022. +//// // -// PutRequestTests.swift +//import Foundation +//import Testing +//import Combine // +//@testable +//import Networking // -// Created by Sacha DSO on 12/04/2022. +//@Suite(.serialized) +//class PutRequestCombineTests { +// +// private let network = NetworkingClient(baseURL: "https://mocked.com") +// private var cancellables = Set() // - -import Foundation -import Testing -import Combine - -@testable -import Networking - -@Suite(.serialized) -class PutRequestCombineTests { - - private let network = NetworkingClient(baseURL: "https://mocked.com") - private var cancellables = Set() - - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] - } - - @Test - func PUTVoidWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let _: Void = await testHelper(network.put("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - } - - @Test - func PUTDataWorks() async { - MockingURLProtocol.mockedResponse = - """ - { "response": "OK" } - """ - let data: Data = await testHelper(network.put("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - } - - @Test - func testPUTJSONWorks() async { - MockingURLProtocol.mockedResponse = - """ - {"response":"OK"} - """ - let json: JSON = await testHelper(network.put("/users")) - - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) - let expectedResponseData = - """ - {"response":"OK"} - """.data(using: String.Encoding.utf8) - #expect(data == expectedResponseData) - } - - @Test - func PUTNetworkingJSONDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "title":"Hello", - "content":"World", - } - """ - let post: Post = await testHelper(network.put("/posts/1")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") - #expect(post.title == "Hello") - #expect(post.content == "World") - } - - @Test - func PUTDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "firstname":"John", - "lastname":"Doe", - } - """ - let userJSON: UserJSON = await testHelper(network.put("/users/1")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") - #expect(userJSON.firstname == "John") - #expect(userJSON.lastname == "Doe") - } - - @Test - func PUTArrayOfDecodableWorks() async { - MockingURLProtocol.mockedResponse = - """ - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - """ - let userJSON: [UserJSON] = await testHelper(network.put("/users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - } - - @Test - func PUTArrayOfDecodableWithKeypathWorks() async { - MockingURLProtocol.mockedResponse = - """ - { - "users" : - [ - { - "firstname":"John", - "lastname":"Doe" - }, - { - "firstname":"Jimmy", - "lastname":"Punchline" - } - ] - } - """ - let userJSON: [UserJSON] = await testHelper(network.put("/users", keypath: "users")) - #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") - #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - #expect(userJSON[0].firstname == "John") - #expect(userJSON[0].lastname == "Doe") - #expect(userJSON[1].firstname == "Jimmy") - #expect(userJSON[1].lastname == "Punchline") - } - - func testHelper(_ publisher: AnyPublisher) async -> T { - return await withCheckedContinuation { continuation in - publisher.sink { completion in - switch completion { - case .failure(_): - Issue.record("failure") - case .finished: - print("finished") - } - } receiveValue: { x in - continuation.resume(returning: x) - } - .store(in: &cancellables) - } - } -} +// init() { +// network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] +// } +// +// @Test +// func PUTVoidWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let _: Void = await testHelper(network.put("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// } +// +// @Test +// func PUTDataWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { "response": "OK" } +// """ +// let data: Data = await testHelper(network.put("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(data == MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) +// } +// +// @Test +// func testPUTJSONWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// {"response":"OK"} +// """ +// let json: JSON = await testHelper(network.put("/users")) +// +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) +// let expectedResponseData = +// """ +// {"response":"OK"} +// """.data(using: String.Encoding.utf8) +// #expect(data == expectedResponseData) +// } +// +// @Test +// func PUTNetworkingJSONDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "title":"Hello", +// "content":"World", +// } +// """ +// let post: Post = await testHelper(network.put("/posts/1")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/posts/1") +// #expect(post.title == "Hello") +// #expect(post.content == "World") +// } +// +// @Test +// func PUTDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "firstname":"John", +// "lastname":"Doe", +// } +// """ +// let userJSON: UserJSON = await testHelper(network.put("/users/1")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users/1") +// #expect(userJSON.firstname == "John") +// #expect(userJSON.lastname == "Doe") +// } +// +// @Test +// func PUTArrayOfDecodableWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// """ +// let userJSON: [UserJSON] = await testHelper(network.put("/users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// } +// +// @Test +// func PUTArrayOfDecodableWithKeypathWorks() async { +// MockingURLProtocol.mockedResponse = +// """ +// { +// "users" : +// [ +// { +// "firstname":"John", +// "lastname":"Doe" +// }, +// { +// "firstname":"Jimmy", +// "lastname":"Punchline" +// } +// ] +// } +// """ +// let userJSON: [UserJSON] = await testHelper(network.put("/users", keypath: "users")) +// #expect(MockingURLProtocol.currentRequest?.httpMethod == "PUT") +// #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") +// #expect(userJSON[0].firstname == "John") +// #expect(userJSON[0].lastname == "Doe") +// #expect(userJSON[1].firstname == "Jimmy") +// #expect(userJSON[1].lastname == "Punchline") +// } +// +// func testHelper(_ publisher: AnyPublisher) async -> T { +// return await withCheckedContinuation { continuation in +// publisher.sink { completion in +// switch completion { +// case .failure(_): +// Issue.record("failure") +// case .finished: +// print("finished") +// } +// } receiveValue: { x in +// continuation.resume(returning: x) +// } +// .store(in: &cancellables) +// } +// } +//} diff --git a/Tests/NetworkingTests/DeleteRequestTests.swift b/Tests/NetworkingTests/DeleteRequestTests.swift index 8655bca..aabff7b 100644 --- a/Tests/NetworkingTests/DeleteRequestTests.swift +++ b/Tests/NetworkingTests/DeleteRequestTests.swift @@ -14,8 +14,8 @@ struct DeleteRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } @Test diff --git a/Tests/NetworkingTests/GetRequestTests.swift b/Tests/NetworkingTests/GetRequestTests.swift index 78d9201..7d34d5e 100644 --- a/Tests/NetworkingTests/GetRequestTests.swift +++ b/Tests/NetworkingTests/GetRequestTests.swift @@ -14,8 +14,8 @@ struct GetRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } @Test diff --git a/Tests/NetworkingTests/MultipartRequestTests.swift b/Tests/NetworkingTests/MultipartRequestTests.swift index d6cb073..be006b7 100644 --- a/Tests/NetworkingTests/MultipartRequestTests.swift +++ b/Tests/NetworkingTests/MultipartRequestTests.swift @@ -28,7 +28,7 @@ final class MultipartRequestTests { mimeType: "text/plain") // Construct request - var request = baseClient.createRequest(.post, route, params: params) + var request = await baseClient.createRequest(.post, route, params: params) request.multipartData = [multipartData] if let urlRequest = request.buildURLRequest(), @@ -58,7 +58,7 @@ final class MultipartRequestTests { mimeType: "text/plain") // Construct request - var request = baseClient.createRequest(.post, route, params: params) + var request = await baseClient.createRequest(.post, route, params: params) request.multipartData = [multipartData] if let urlRequest = request.buildURLRequest(), @@ -95,7 +95,7 @@ final class MultipartRequestTests { ] // Construct request - var request = baseClient.createRequest(.post, route, params: params) + var request = await baseClient.createRequest(.post, route, params: params) request.multipartData = multipartData if let urlRequest = request.buildURLRequest(), diff --git a/Tests/NetworkingTests/PatchRequestTests.swift b/Tests/NetworkingTests/PatchRequestTests.swift index c877746..a81d452 100644 --- a/Tests/NetworkingTests/PatchRequestTests.swift +++ b/Tests/NetworkingTests/PatchRequestTests.swift @@ -14,8 +14,8 @@ struct PatchRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } @@ -48,10 +48,10 @@ struct PatchRequestTests { """ {"response":"OK"} """ - let json: Any = try await network.patch("/users") + let json: JSON = try await network.patch("/users") #expect(MockingURLProtocol.currentRequest?.httpMethod == "PATCH") #expect(MockingURLProtocol.currentRequest?.url?.absoluteString == "https://mocked.com/users") - let data = try? JSONSerialization.data(withJSONObject: json, options: []) + let data = try? JSONSerialization.data(withJSONObject: json.value, options: []) let expectedResponseData = """ {"response":"OK"} diff --git a/Tests/NetworkingTests/PostRequestTests.swift b/Tests/NetworkingTests/PostRequestTests.swift index 4df62ab..52ffa04 100644 --- a/Tests/NetworkingTests/PostRequestTests.swift +++ b/Tests/NetworkingTests/PostRequestTests.swift @@ -14,8 +14,8 @@ struct PostRequestTests { private let network = NetworkingClient(baseURL: "https://mocked.com") - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } @Test diff --git a/Tests/NetworkingTests/PutRequestTests.swift b/Tests/NetworkingTests/PutRequestTests.swift index e0beaa6..4e21408 100644 --- a/Tests/NetworkingTests/PutRequestTests.swift +++ b/Tests/NetworkingTests/PutRequestTests.swift @@ -40,8 +40,8 @@ struct PutRequestTests { private let api = FakeAPI() - init() { - network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] + init() async { + await network.sessionConfiguration.protocolClasses = [MockingURLProtocol.self] } @Test From e7849706a4928f6ad9ce1aae99c3de1ad67774d1 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Mon, 3 Nov 2025 12:08:48 +0100 Subject: [PATCH 25/25] Updates example project --- .../Swift6Arch.xcodeproj/project.pbxproj | 33 +++++++++---------- .../Swift6Arch/Domain/UserRepository.swift | 2 +- .../Swift6Arch/Domain/UserService.swift | 1 + .../JSONAPIUserRepository.swift | 10 ++++-- Swift6Arch/Swift6Arch/Swift6ArchApp.swift | 5 +-- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj b/Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj index 7a59627..b361088 100644 --- a/Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj +++ b/Swift6Arch/Swift6Arch.xcodeproj/project.pbxproj @@ -7,12 +7,11 @@ objects = { /* Begin PBXBuildFile section */ - 639F73972CAE0DF1008287D5 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 639F73962CAE0DF1008287D5 /* Networking */; }; + 99EBF98F2EB8C43700C95827 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 99EBF98E2EB8C43700C95827 /* Networking */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 639F73832CAE084D008287D5 /* Swift6Arch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Swift6Arch.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 639F73942CAE0864008287D5 /* Networking */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Networking; path = /Users/sacha.durand/Projects/Networking; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -28,7 +27,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 639F73972CAE0DF1008287D5 /* Networking in Frameworks */, + 99EBF98F2EB8C43700C95827 /* Networking in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -38,7 +37,6 @@ 639F737A2CAE084D008287D5 = { isa = PBXGroup; children = ( - 639F73942CAE0864008287D5 /* Networking */, 639F73852CAE084D008287D5 /* Swift6Arch */, 639F73842CAE084D008287D5 /* Products */, ); @@ -72,7 +70,7 @@ ); name = Swift6Arch; packageProductDependencies = ( - 639F73962CAE0DF1008287D5 /* Networking */, + 99EBF98E2EB8C43700C95827 /* Networking */, ); productName = Swift6Arch; productReference = 639F73832CAE084D008287D5 /* Swift6Arch.app */; @@ -86,7 +84,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1600; - LastUpgradeCheck = 1600; + LastUpgradeCheck = 2600; TargetAttributes = { 639F73822CAE084D008287D5 = { CreatedOnToolsVersion = 16.0; @@ -103,7 +101,7 @@ mainGroup = 639F737A2CAE084D008287D5; minimizedProjectReferenceProxies = 1; packageReferences = ( - 639F73952CAE0DF0008287D5 /* XCRemoteSwiftPackageReference "Networking" */, + 99EBF98D2EB8C43700C95827 /* XCLocalSwiftPackageReference "../../Networking" */, ); preferredProjectObjectVersion = 77; productRefGroup = 639F73842CAE084D008287D5 /* Products */; @@ -194,6 +192,7 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 6.0; @@ -251,6 +250,7 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_VERSION = 6.0; VALIDATE_PRODUCT = YES; @@ -279,6 +279,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sacha.Swift6Arch; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -307,6 +308,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sacha.Swift6Arch; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -336,21 +338,16 @@ }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - 639F73952CAE0DF0008287D5 /* XCRemoteSwiftPackageReference "Networking" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/freshOS/Networking"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.3; - }; +/* Begin XCLocalSwiftPackageReference section */ + 99EBF98D2EB8C43700C95827 /* XCLocalSwiftPackageReference "../../Networking" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../Networking; }; -/* End XCRemoteSwiftPackageReference section */ +/* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 639F73962CAE0DF1008287D5 /* Networking */ = { + 99EBF98E2EB8C43700C95827 /* Networking */ = { isa = XCSwiftPackageProductDependency; - package = 639F73952CAE0DF0008287D5 /* XCRemoteSwiftPackageReference "Networking" */; productName = Networking; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Swift6Arch/Swift6Arch/Domain/UserRepository.swift b/Swift6Arch/Swift6Arch/Domain/UserRepository.swift index 34971d9..a4ccee7 100644 --- a/Swift6Arch/Swift6Arch/Domain/UserRepository.swift +++ b/Swift6Arch/Swift6Arch/Domain/UserRepository.swift @@ -7,6 +7,6 @@ import Foundation -protocol UserRepository { +protocol UserRepository: Sendable { func fetchCurrentUser() async throws -> User } diff --git a/Swift6Arch/Swift6Arch/Domain/UserService.swift b/Swift6Arch/Swift6Arch/Domain/UserService.swift index 7132646..60161ec 100644 --- a/Swift6Arch/Swift6Arch/Domain/UserService.swift +++ b/Swift6Arch/Swift6Arch/Domain/UserService.swift @@ -10,6 +10,7 @@ import Foundation actor UserService { let userRepository: UserRepository + init(userRepository: UserRepository) { self.userRepository = userRepository } diff --git a/Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift b/Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift index 45e6b59..15506a5 100644 --- a/Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift +++ b/Swift6Arch/Swift6Arch/Infrastructure/JSONAPIUserRepository.swift @@ -8,12 +8,16 @@ import Foundation import Networking -struct JSONAPIUserRepository: UserRepository { +struct JSONAPIUserRepository: UserRepository, NetworkingService { - let network = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") + let network: NetworkingClient + + init(client: NetworkingClient) { + self.network = client + } func fetchCurrentUser() async throws -> User { - let userJSON: UserJSON = try await network.get("/users/1") + let userJSON: UserJSON = try await get("/users/1") return User(name: userJSON.name) } } diff --git a/Swift6Arch/Swift6Arch/Swift6ArchApp.swift b/Swift6Arch/Swift6Arch/Swift6ArchApp.swift index a7f8d68..9d7137e 100644 --- a/Swift6Arch/Swift6Arch/Swift6ArchApp.swift +++ b/Swift6Arch/Swift6Arch/Swift6ArchApp.swift @@ -6,14 +6,15 @@ // import SwiftUI +import Networking @main struct Swift6ArchApp: App { - let userService = UserService(userRepository: JSONAPIUserRepository()) + let userService = UserService(userRepository: JSONAPIUserRepository(client: NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com"))) var body: some Scene { WindowGroup { ContentComponent(userService: userService) - } + } } }