From d7dad91c0bcbd820199b3a2ca0949b3e0e2fe5a2 Mon Sep 17 00:00:00 2001 From: Elvis <elvisnunez@me.com> Date: Fri, 19 Jul 2024 18:36:26 +0200 Subject: [PATCH 1/5] Fix Void return for fake --- Sources/Networking/Networking+New.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Networking/Networking+New.swift b/Sources/Networking/Networking+New.swift index e6e7505..8303113 100644 --- a/Sources/Networking/Networking+New.swift +++ b/Sources/Networking/Networking+New.swift @@ -17,7 +17,7 @@ extension Networking { case .success(let response): if T.self == Data.self { logger.info("Successfully processed fake request to \(path, privacy: .public)") - return .success(() as! T) + return .success(Data() as! T) } else { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 From dba6e3a70160a70cec4ac0068c41f2fd4ea0f600 Mon Sep 17 00:00:00 2001 From: Elvis <elvisnunez@me.com> Date: Fri, 19 Jul 2024 20:20:16 +0200 Subject: [PATCH 2/5] Fix/test faking for new methods --- Sources/Networking/Networking+New.swift | 7 +++++ Tests/NetworkingTests/FakeRequestTests.swift | 33 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Sources/Networking/Networking+New.swift b/Sources/Networking/Networking+New.swift index 8303113..d03955d 100644 --- a/Sources/Networking/Networking+New.swift +++ b/Sources/Networking/Networking+New.swift @@ -18,6 +18,13 @@ extension Networking { if T.self == Data.self { logger.info("Successfully processed fake request to \(path, privacy: .public)") return .success(Data() as! T) + } else if T.self == NetworkingResponse.self { + let headers = Dictionary(uniqueKeysWithValues: response.headers.compactMap { key, value in + (key as? String).map { ($0, AnyCodable(value)) } + }) + let body = try JSONDecoder().decode([String: AnyCodable].self, from: response.data) + let networkingJSON = NetworkingResponse(headers: headers, body: body) + return .success(networkingJSON as! T) } else { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 diff --git a/Tests/NetworkingTests/FakeRequestTests.swift b/Tests/NetworkingTests/FakeRequestTests.swift index dc2e27f..ef2af4a 100644 --- a/Tests/NetworkingTests/FakeRequestTests.swift +++ b/Tests/NetworkingTests/FakeRequestTests.swift @@ -683,4 +683,37 @@ extension FakeRequestTests { XCTFail(response.error.localizedDescription) } } + + func testNewPostWithFakeHeaders() async { + let networking = Networking(baseURL: baseURL) + + networking.fakePOST("/auth/verify_confirmation_code", response: [ + "phone_number": "phoneNumber" + ], headerFields: [ + "client": "aClient", + "access-token": "anAccessToken", + "uid": "aUID", + "Authorization": "authorization", + ]) + + let parameters: [String: Any] = [ + "phone_number": "phoneNumber", + "confirmation_code": "confirmationCode" + ] + + let result: Result<NetworkingResponse, NetworkingError> = await networking.newPost("/auth/verify_confirmation_code", parameters: parameters) + switch result { + case .success(let response): + let headers = response.headers + XCTAssertEqual(headers.string(for: "access-token"), "anAccessToken") + XCTAssertEqual(headers.string(for: "client"), "aClient") + XCTAssertEqual(headers.string(for: "uid"), "aUID") + XCTAssertEqual(headers.string(for: "Authorization"), "authorization") + + let body = response.body + XCTAssertEqual(body.string(for: "phone_number"), "phoneNumber") + case .failure (let response): + XCTFail(response.localizedDescription) + } + } } From 5aaa89fef02602f435bb39d891585c7f30edf7df Mon Sep 17 00:00:00 2001 From: Elvis <elvisnunez@me.com> Date: Mon, 5 Aug 2024 01:24:57 +0200 Subject: [PATCH 3/5] Add newPatch --- Sources/Networking/Networking+HTTPRequests.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/Networking/Networking+HTTPRequests.swift b/Sources/Networking/Networking+HTTPRequests.swift index ef05626..d3d0c64 100644 --- a/Sources/Networking/Networking+HTTPRequests.swift +++ b/Sources/Networking/Networking+HTTPRequests.swift @@ -73,6 +73,20 @@ public extension Networking { } } + func newPatch<T: Decodable>(_ path: String, parameters: [String: Any]) async -> Result<T, NetworkingError> { + return await handle(.patch, path: path, parameters: parameters) + } + + func newPatch(_ path: String, parameters: [String: Any]) async -> Result<Void, NetworkingError> { + let result: Result<Data, NetworkingError> = await handle(.patch, path: path, parameters: parameters) + switch result { + case .success: + return .success(()) + case .failure(let error): + return .failure(error) + } + } + func newDelete(_ path: String) async -> Result<Void, NetworkingError> { let result: Result<Data, NetworkingError> = await handle(.delete, path: path, parameters: nil) switch result { From 28f479ef6738fb90c2f0b91fce4d13f8c74073cb Mon Sep 17 00:00:00 2001 From: Elvis Nunez <3lvis@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:22:05 +0200 Subject: [PATCH 4/5] Improve Fake handling, add status code to faking with a file. (#280) * Improve fake handling * Improve fake handling, add status code to faking with file * Remove dumb comparison * Rename none to data --- Sources/Networking/JSON.swift | 15 +- .../Networking/Networking+HTTPRequests.swift | 20 +- Sources/Networking/Networking+New.swift | 204 +++++++++--------- Sources/Networking/Networking+Private.swift | 4 +- Sources/Networking/NetworkingResult.swift | 9 +- Sources/Networking/Response.swift | 2 +- Tests/NetworkingTests/GETTests.swift | 39 ---- Tests/NetworkingTests/JSONTests.swift | 3 +- .../NetworkingTests/NewNetworkingTests.swift | 64 ++++++ Tests/NetworkingTests/ResultTests.swift | 10 +- 10 files changed, 201 insertions(+), 169 deletions(-) create mode 100644 Tests/NetworkingTests/NewNetworkingTests.swift diff --git a/Sources/Networking/JSON.swift b/Sources/Networking/JSON.swift index 019c005..c919773 100644 --- a/Sources/Networking/JSON.swift +++ b/Sources/Networking/JSON.swift @@ -6,7 +6,7 @@ enum ParsingError: Error { } enum JSON: Equatable { - case none + case data(Data) case dictionary(Data, [String: Any]) @@ -30,6 +30,17 @@ enum JSON: Equatable { } } + var data: Data { + switch self { + case let .dictionary(data, _): + return data + case let .array(data, _): + return data + case let .data(data): + return data + } + } + init(_ data: Data) throws { let body = try JSONSerialization.jsonObject(with: data, options: []) @@ -38,7 +49,7 @@ enum JSON: Equatable { } else if let array = body as? [[String: Any]] { self = .array(data, array) } else { - self = JSON.none + self = .data(data) } } diff --git a/Sources/Networking/Networking+HTTPRequests.swift b/Sources/Networking/Networking+HTTPRequests.swift index d3d0c64..85ef829 100644 --- a/Sources/Networking/Networking+HTTPRequests.swift +++ b/Sources/Networking/Networking+HTTPRequests.swift @@ -29,8 +29,8 @@ public extension Networking { /// - path: The path for the faked GET request. /// - fileName: The name of the file, whose contents will be registered as a reponse. /// - bundle: The Bundle where the file is located. - func fakeGET(_ path: String, fileName: String, bundle: Bundle = Bundle.main, delay: Double = 0) { - registerFake(requestType: .get, path: path, fileName: fileName, bundle: bundle, delay: delay) + func fakeGET(_ path: String, fileName: String, bundle: Bundle = Bundle.main, statusCode: Int = 200, delay: Double = 0) { + registerFake(requestType: .get, path: path, fileName: fileName, bundle: bundle, statusCode: statusCode, delay: delay) } /// Cancels the GET request for the specified path. This causes the request to complete with error code URLError.cancelled. @@ -127,8 +127,8 @@ public extension Networking { /// - path: The path for the faked PATCH request. /// - fileName: The name of the file, whose contents will be registered as a reponse. /// - bundle: The Bundle where the file is located. - func fakePATCH(_ path: String, fileName: String, bundle: Bundle = Bundle.main, delay: Double = 0) { - registerFake(requestType: .patch, path: path, fileName: fileName, bundle: bundle, delay: delay) + func fakePATCH(_ path: String, fileName: String, bundle: Bundle = Bundle.main, statusCode: Int = 200, delay: Double = 0) { + registerFake(requestType: .patch, path: path, fileName: fileName, bundle: bundle, statusCode: statusCode, delay: delay) } /// Cancels the PATCH request for the specified path. This causes the request to complete with error code URLError.cancelled. @@ -169,8 +169,8 @@ public extension Networking { /// - path: The path for the faked PUT request. /// - fileName: The name of the file, whose contents will be registered as a reponse. /// - bundle: The Bundle where the file is located. - func fakePUT(_ path: String, fileName: String, bundle: Bundle = Bundle.main, delay: Double = 0) { - registerFake(requestType: .put, path: path, fileName: fileName, bundle: bundle, delay: delay) + func fakePUT(_ path: String, fileName: String, bundle: Bundle = Bundle.main, statusCode: Int = 200, delay: Double = 0) { + registerFake(requestType: .put, path: path, fileName: fileName, bundle: bundle, statusCode: statusCode, delay: delay) } /// Cancels the PUT request for the specified path. This causes the request to complete with error code URLError.cancelled. @@ -221,8 +221,8 @@ public extension Networking { /// - path: The path for the faked POST request. /// - fileName: The name of the file, whose contents will be registered as a reponse. /// - bundle: The Bundle where the file is located. - func fakePOST(_ path: String, fileName: String, bundle: Bundle = Bundle.main, delay: Double = 0) { - registerFake(requestType: .post, path: path, fileName: fileName, bundle: bundle, delay: delay) + func fakePOST(_ path: String, fileName: String, bundle: Bundle = Bundle.main, statusCode: Int = 200, delay: Double = 0) { + registerFake(requestType: .post, path: path, fileName: fileName, bundle: bundle, statusCode: statusCode, delay: delay) } /// Cancels the POST request for the specified path. This causes the request to complete with error code URLError.cancelled. @@ -263,8 +263,8 @@ public extension Networking { /// - path: The path for the faked DELETE request. /// - fileName: The name of the file, whose contents will be registered as a reponse. /// - bundle: The Bundle where the file is located. - func fakeDELETE(_ path: String, fileName: String, bundle: Bundle = Bundle.main, delay: Double = 0) { - registerFake(requestType: .delete, path: path, fileName: fileName, bundle: bundle, delay: delay) + func fakeDELETE(_ path: String, fileName: String, bundle: Bundle = Bundle.main, statusCode: Int = 200, delay: Double = 0) { + registerFake(requestType: .delete, path: path, fileName: fileName, bundle: bundle, statusCode: statusCode, delay: delay) } /// Cancels the DELETE request for the specified path. This causes the request to complete with error code URLError.cancelled. diff --git a/Sources/Networking/Networking+New.swift b/Sources/Networking/Networking+New.swift index d03955d..d7a6f0a 100644 --- a/Sources/Networking/Networking+New.swift +++ b/Sources/Networking/Networking+New.swift @@ -2,122 +2,112 @@ import Foundation extension Networking { func handle<T: Decodable>(_ requestType: RequestType, path: String, parameters: Any?) async -> Result<T, NetworkingError> { - var data: Data? do { logger.info("Starting \(requestType.rawValue) request to \(path, privacy: .public)") if let fakeRequest = try FakeRequest.find(ofType: requestType, forPath: path, in: fakeRequests) { - let (_, response, error) = try handleFakeRequest(fakeRequest, path: path, cacheName: nil, cachingLevel: .none) - if fakeRequest.delay > 0 { - let nanoseconds = UInt64(fakeRequest.delay * 1_000_000_000) - try? await Task.sleep(nanoseconds: nanoseconds) - } - let result = try JSONResult(body: fakeRequest.response, response: response, error: error) - switch result { - case .success(let response): - if T.self == Data.self { - logger.info("Successfully processed fake request to \(path, privacy: .public)") - return .success(Data() as! T) - } else if T.self == NetworkingResponse.self { - let headers = Dictionary(uniqueKeysWithValues: response.headers.compactMap { key, value in - (key as? String).map { ($0, AnyCodable(value)) } - }) - let body = try JSONDecoder().decode([String: AnyCodable].self, from: response.data) - let networkingJSON = NetworkingResponse(headers: headers, body: body) - return .success(networkingJSON as! T) - } else { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - let decodedResponse = try decoder.decode(T.self, from: response.data) - logger.info("Successfully decoded response from fake request to \(path, privacy: .public)") - return .success(decodedResponse) - } - case .failure(let response): - logger.error("Failed to process fake request to \(path, privacy: .public): \(response.error.localizedDescription, privacy: .public)") - return .failure(.unexpectedError(statusCode: nil, message: "Failed to process fake request (error: \(response.error.localizedDescription)).")) - } + return try await handleFakeRequest(fakeRequest, path: path, requestType: requestType) } - let parameterType: Networking.ParameterType? = parameters != nil ? .json : nil - var request = URLRequest(url: try composedURL(with: path), requestType: requestType, path: path, parameterType: parameterType, responseType: .json, boundary: boundary, authorizationHeaderValue: authorizationHeaderValue, token: token, authorizationHeaderKey: authorizationHeaderKey, headerFields: headerFields) + let request = try createRequest(path: path, requestType: requestType, parameters: parameters) + let (responseData, response) = try await session.data(for: request) + return try handleResponse(responseData: responseData, response: response, path: path) - if let parameters = parameters { - request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: []) - } + } catch { + return handleRequestError(error: error) + } + } - let (responseData, response) = try await session.data(for: request) - data = responseData - guard let httpResponse = response as? HTTPURLResponse else { - logger.error("Invalid response received from \(path, privacy: .public)") - return .failure(.invalidResponse) - } + private func handleFakeRequest<T: Decodable>(_ fakeRequest: FakeRequest, path: String, requestType: RequestType) async throws -> Result<T, NetworkingError> { + let (_, response, error) = try handleFakeRequest(fakeRequest, path: path, cacheName: nil, cachingLevel: .none) - let statusCode = httpResponse.statusCode - switch statusCode.statusCodeType { - case .informational, .successful: - logger.info("Received successful response with status code \(statusCode) from \(path, privacy: .public)") - if T.self == Data.self { - return .success(Data() as! T) - } else if T.self == NetworkingResponse.self { - let headers = Dictionary(uniqueKeysWithValues: httpResponse.allHeaderFields.compactMap { key, value in - (key as? String).map { ($0, AnyCodable(value)) } - }) - let body = try JSONDecoder().decode([String: AnyCodable].self, from: responseData) - let networkingJSON = NetworkingResponse(headers: headers, body: body) - return .success(networkingJSON as! T) - } else { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - let decodedResponse = try decoder.decode(T.self, from: responseData) - return .success(decodedResponse) - } - case .redirection: - logger.warning("Redirection response with status code \(statusCode) from \(path, privacy: .public)") - return .failure(.unexpectedError(statusCode: statusCode, message: "Redirection occurred.")) - case .clientError: - let errorMessage = HTTPURLResponse.localizedString(forStatusCode: statusCode) - if let jsonString = String(data: responseData, encoding: .utf8) { - logger.warning("Client error: \(jsonString, privacy: .public) with status code \(statusCode) from \(path, privacy: .public)") - } - if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: responseData) { - logger.warning("Client error: \(errorResponse.combinedMessage, privacy: .public) with status code \(statusCode) from \(path, privacy: .public)") - return .failure(.clientError(statusCode: statusCode, message: errorResponse.combinedMessage)) - } else { - logger.warning("Client error: \(errorMessage, privacy: .public) with status code \(statusCode) from \(path, privacy: .public)") - return .failure(.clientError(statusCode: statusCode, message: errorMessage)) - } - case .serverError: - let errorMessage = HTTPURLResponse.localizedString(forStatusCode: statusCode) - var errorDetails: [String: Any]? = nil - if let jsonString = String(data: responseData, encoding: .utf8) { - logger.error("Server error: \(jsonString, privacy: .public) with status code \(statusCode) from \(path, privacy: .public)") - } - if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: responseData) { - errorDetails = ["error": errorResponse.error ?? "", - "message": errorResponse.message ?? "", - "errors": errorResponse.errors ?? [:]] - logger.error("Server error: \(errorResponse.combinedMessage, privacy: .public) with status code \(statusCode) from \(path, privacy: .public)") - return .failure(.serverError(statusCode: statusCode, message: errorResponse.combinedMessage, details: errorDetails)) - } else { - logger.error("Server error: \(errorMessage, privacy: .public) with status code \(statusCode) from \(path, privacy: .public)") - return .failure(.serverError(statusCode: statusCode, message: errorMessage, details: errorDetails)) - } - case .cancelled: - logger.info("Request cancelled with status code \(statusCode) from \(path, privacy: .public)") - return .failure(.unexpectedError(statusCode: statusCode, message: "Request was cancelled.")) - case .unknown: - logger.error("Unexpected error with status code \(statusCode) from \(path, privacy: .public)") - return .failure(.unexpectedError(statusCode: statusCode, message: "An unexpected error occurred.")) - } - } catch let error as NSError { - if let data = data, let jsonString = String(data: data, encoding: .utf8) { - logger.error("Unexpected error occurred: \(error.localizedDescription, privacy: .public). Response data: \(jsonString, privacy: .public)") - } else if let decodingError = error as? DecodingError { - logger.error("Unexpected error occurred: \(decodingError.detailedMessage, privacy: .public)") - } else { - logger.error("Unexpected error occurred: \(error.localizedDescription, privacy: .public)") - } - return .failure(.unexpectedError(statusCode: nil, message: "Failed to process request (error: \(error.localizedDescription)).")) + if fakeRequest.delay > 0 { + try? await Task.sleep(nanoseconds: UInt64(fakeRequest.delay * 1_000_000_000)) + } + + let result = try JSONResult(body: fakeRequest.response, response: response, error: error) + return try handleResponse(responseData: result.data, response: response, path: path) + } + + private func createRequest(path: String, requestType: RequestType, parameters: Any?) throws -> URLRequest { + let parameterType: Networking.ParameterType? = parameters != nil ? .json : nil + var request = URLRequest(url: try composedURL(with: path), requestType: requestType, path: path, parameterType: parameterType, responseType: .json, boundary: boundary, authorizationHeaderValue: authorizationHeaderValue, token: token, authorizationHeaderKey: authorizationHeaderKey, headerFields: headerFields) + + if let parameters = parameters { + request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: []) + } + + return request + } + + private func handleResponse<T: Decodable>(responseData: Data, response: URLResponse, path: String) throws -> Result<T, NetworkingError> { + guard let httpResponse = response as? HTTPURLResponse else { + return .failure(.invalidResponse) + } + + let statusCode = httpResponse.statusCode + switch statusCode.statusCodeType { + case .informational, .successful: + return try handleSuccessfulResponse(responseData: responseData, path: path, httpResponse: httpResponse) + case .redirection: + return .failure(.unexpectedError(statusCode: statusCode, message: "Redirection occurred.")) + case .clientError: + return try handleClientError(responseData: responseData, statusCode: statusCode, path: path) + case .serverError: + return try handleServerError(responseData: responseData, statusCode: statusCode, path: path) + case .cancelled: + return .failure(.unexpectedError(statusCode: statusCode, message: "Request was cancelled.")) + case .unknown: + return .failure(.unexpectedError(statusCode: statusCode, message: "An unexpected error occurred.")) + } + } + + private func handleSuccessfulResponse<T: Decodable>(responseData: Data, path: String, httpResponse: HTTPURLResponse) throws -> Result<T, NetworkingError> { + if T.self == Data.self { + return .success(Data() as! T) + } else if T.self == NetworkingResponse.self { + let headers = Dictionary(uniqueKeysWithValues: httpResponse.allHeaderFields.compactMap { key, value in + (key as? String).map { ($0, AnyCodable(value)) } + }) + let body = try JSONDecoder().decode([String: AnyCodable].self, from: responseData) + let networkingJSON = NetworkingResponse(headers: headers, body: body) + return .success(networkingJSON as! T) + } else { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + let decodedResponse = try decoder.decode(T.self, from: responseData) + return .success(decodedResponse) + } + } + + private func handleClientError<T: Decodable>(responseData: Data, statusCode: Int, path: String) throws -> Result<T, NetworkingError> { + let errorMessage = HTTPURLResponse.localizedString(forStatusCode: statusCode) + if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: responseData) { + return .failure(.clientError(statusCode: statusCode, message: errorResponse.combinedMessage)) + } else { + return .failure(.clientError(statusCode: statusCode, message: errorMessage)) + } + } + + private func handleServerError<T: Decodable>(responseData: Data, statusCode: Int, path: String) throws -> Result<T, NetworkingError> { + let errorMessage = HTTPURLResponse.localizedString(forStatusCode: statusCode) + var errorDetails: [String: Any]? = nil + if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: responseData) { + errorDetails = ["error": errorResponse.error ?? "", + "message": errorResponse.message ?? "", + "errors": errorResponse.errors ?? [:]] + return .failure(.serverError(statusCode: statusCode, message: errorResponse.combinedMessage, details: errorDetails)) + } else { + return .failure(.serverError(statusCode: statusCode, message: errorMessage, details: errorDetails)) + } + } + + private func handleRequestError<T: Decodable>(error: Error) -> Result<T, NetworkingError> { + if let decodingError = error as? DecodingError { + logger.error("Unexpected error occurred: \(decodingError.detailedMessage, privacy: .public)") + } else { + logger.error("Unexpected error occurred: \(error.localizedDescription, privacy: .public)") } + return .failure(.unexpectedError(statusCode: nil, message: "Failed to process request (error: \(error.localizedDescription)).")) } } diff --git a/Sources/Networking/Networking+Private.swift b/Sources/Networking/Networking+Private.swift index 4deab31..7c72891 100644 --- a/Sources/Networking/Networking+Private.swift +++ b/Sources/Networking/Networking+Private.swift @@ -39,10 +39,10 @@ extension Networking { } } - func registerFake(requestType: RequestType, path: String, fileName: String, bundle: Bundle, delay: Double) { + func registerFake(requestType: RequestType, path: String, fileName: String, bundle: Bundle, statusCode: Int, delay: Double) { do { if let result = try FileManager.json(from: fileName, bundle: bundle) { - registerFake(requestType: requestType, path: path, headerFields: nil, response: result, responseType: .json, statusCode: 200, delay: delay) + registerFake(requestType: requestType, path: path, headerFields: nil, response: result, responseType: .json, statusCode: statusCode, delay: delay) } } catch ParsingError.notFound { fatalError("We couldn't find \(fileName), are you sure is there?") diff --git a/Sources/Networking/NetworkingResult.swift b/Sources/Networking/NetworkingResult.swift index 648550b..b3c55a4 100644 --- a/Sources/Networking/NetworkingResult.swift +++ b/Sources/Networking/NetworkingResult.swift @@ -19,6 +19,13 @@ public enum JSONResult: NetworkingResult { case failure(FailureJSONResponse) + public var data: Data { + switch self { + case .success(let response): return response.json.data + case .failure(let response): return response.json.data + } + } + public var error: NSError? { switch self { case .success: @@ -30,7 +37,7 @@ public enum JSONResult: NetworkingResult { public init(body: Any?, response: HTTPURLResponse, error: NSError?) throws { var returnedError = error - var json = JSON.none + var json = JSON.data(Data()) do { if let dictionary = body as? [String: Any] { diff --git a/Sources/Networking/Response.swift b/Sources/Networking/Response.swift index 89e4c04..35bb6b2 100644 --- a/Sources/Networking/Response.swift +++ b/Sources/Networking/Response.swift @@ -44,7 +44,7 @@ public class JSONResponse: Response { return value case let .dictionary(value, _): return value - case .none: + case .data: return Data() } } diff --git a/Tests/NetworkingTests/GETTests.swift b/Tests/NetworkingTests/GETTests.swift index 8099210..0b07038 100644 --- a/Tests/NetworkingTests/GETTests.swift +++ b/Tests/NetworkingTests/GETTests.swift @@ -229,43 +229,4 @@ class GETTests: XCTestCase { XCTFail(response.error.localizedDescription) } } - - func testNewGET() async throws { - let networking = Networking(baseURL: baseURL) - - let result: Result<Friend, NetworkingError> = await networking.newGet("/get") - - switch result { - case .success(let success): - print("worked") - case .failure(let failure): - print(failure.localizedDescription) - } - } - - func testNewPOST() async throws { - let networking = Networking(baseURL: baseURL) - - let result: Result<Void, NetworkingError> = await networking.newPost("/get", parameters: ["String": "String"]) - - switch result { - case .success(let success): - print("worked") - case .failure(let failure): - print(failure.localizedDescription) - } - } - - func testNetworkingJSON() async throws { - let networking = Networking(baseURL: baseURL) - - let result: Result<NetworkingResponse, NetworkingError> = await networking.newGet("/auth") - switch result { - case .success(let success): - let header = success.headers.string(for: "access-token") - let body = success.body.string(for: "id") - case .failure(let failure): - print(failure.localizedDescription) - } - } } diff --git a/Tests/NetworkingTests/JSONTests.swift b/Tests/NetworkingTests/JSONTests.swift index 359e63d..f2253f7 100644 --- a/Tests/NetworkingTests/JSONTests.swift +++ b/Tests/NetworkingTests/JSONTests.swift @@ -26,8 +26,7 @@ class JSONTests: XCTestCase { } func testEqualNone() { - XCTAssertEqual(JSON.none, JSON.none) - XCTAssertNotEqual(JSON.none, try JSON(["hello": "value"])) + XCTAssertNotEqual(JSON.data(Data()), try JSON(["hello": "value"])) } // MARKL - Accessors diff --git a/Tests/NetworkingTests/NewNetworkingTests.swift b/Tests/NetworkingTests/NewNetworkingTests.swift new file mode 100644 index 0000000..be4e7dc --- /dev/null +++ b/Tests/NetworkingTests/NewNetworkingTests.swift @@ -0,0 +1,64 @@ +import Foundation +import XCTest +@testable import Networking + +class NewNetworkingTests: XCTestCase { + let baseURL = "http://httpbin.org" + + func testNewGET() async throws { + let networking = Networking(baseURL: baseURL) + + let result: Result<Friend, NetworkingError> = await networking.newGet("/get") + + switch result { + case .success(_): + print("worked") + case .failure(let failure): + print(failure.localizedDescription) + } + } + + func testNewPOST() async throws { + let networking = Networking(baseURL: baseURL) + + let result: Result<Void, NetworkingError> = await networking.newPost("/get", parameters: ["String": "String"]) + + switch result { + case .success(_): + print("worked") + case .failure(let failure): + print(failure.localizedDescription) + } + } + + func testNetworkingJSON() async throws { + let networking = Networking(baseURL: baseURL) + + let result: Result<NetworkingResponse, NetworkingError> = await networking.newGet("/auth") + switch result { + case .success(let success): + _ = success.headers.string(for: "access-token") + _ = success.body.string(for: "id") + case .failure(let failure): + print(failure.localizedDescription) + } + } + + func testErrorNetworkingJSON() async throws { + let networking = Networking(baseURL: baseURL) + + let response: [String: Any] = [ + "errors": [ + "phone_number": ["has already been taken"] + ] + ] + networking.fakePOST("/auth", response: response, statusCode: 422) + + let result: Result<NetworkingResponse, NetworkingError> = await networking.newPost("/auth", parameters: [:]) + switch result { + case .success(_): break + case .failure(let response): + XCTAssertTrue(response.errorDescription!.contains("has already been taken")) + } + } +} diff --git a/Tests/NetworkingTests/ResultTests.swift b/Tests/NetworkingTests/ResultTests.swift index 5dc2d3f..9c8e68b 100644 --- a/Tests/NetworkingTests/ResultTests.swift +++ b/Tests/NetworkingTests/ResultTests.swift @@ -22,7 +22,7 @@ class ResultTests: XCTestCase { switch value.json { case let .dictionary(_, valueBody): XCTAssertEqual(body.debugDescription, valueBody.debugDescription) - case .array(_, _), .none: + case .array(_, _), .data: XCTFail() } case let .failure(response): @@ -43,7 +43,7 @@ class ResultTests: XCTestCase { case let .array(_, valueBody): XCTAssertEqual(expectedBody.debugDescription, valueBody.debugDescription) // XCTAssertEqual(dataBody.hashValue, expectedBody.hashValue) - case .dictionary(_, _), .none: + case .dictionary(_, _), .data: XCTFail() } case let .failure(response): @@ -65,7 +65,7 @@ class ResultTests: XCTestCase { case let .dictionary(dataBody, valueBody): XCTAssertEqual(dataBody.hashValue, expectedBodyData.hashValue) XCTAssertEqual(valueBody.debugDescription, expectedBody.debugDescription) - case .array(_, _), .none: + case .array(_, _), .data: XCTFail() } case let .failure(response): @@ -87,7 +87,7 @@ class ResultTests: XCTestCase { case let .array(dataBody, valueBody): XCTAssertEqual(dataBody.hashValue, expectedBodyData.hashValue) XCTAssertEqual(valueBody.debugDescription, expectedBody.debugDescription) - case .dictionary(_, _), .none: + case .dictionary(_, _), .data: XCTFail() } case let .failure(response): @@ -106,7 +106,7 @@ class ResultTests: XCTestCase { switch value.json { case .dictionary(_, _), .array: XCTFail() - case .none: + case .data: break } case let .failure(response): From 127ac618bc264074c2c6edaa9d496b5519deef4c Mon Sep 17 00:00:00 2001 From: Elvis Nunez <3lvis@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:31:11 +0200 Subject: [PATCH 5/5] Now supporting get parameters (#281) --- .../Networking/Networking+HTTPRequests.swift | 4 +-- Sources/Networking/Networking+New.swift | 31 +++++++++++++++++-- .../NetworkingTests/NewNetworkingTests.swift | 24 ++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/Sources/Networking/Networking+HTTPRequests.swift b/Sources/Networking/Networking+HTTPRequests.swift index 85ef829..1cef7c1 100644 --- a/Sources/Networking/Networking+HTTPRequests.swift +++ b/Sources/Networking/Networking+HTTPRequests.swift @@ -41,8 +41,8 @@ public extension Networking { await cancelRequest(.data, requestType: .get, url: url) } - func newGet<T: Decodable>(_ path: String) async -> Result<T, NetworkingError> { - return await handle(.get, path: path, parameters: nil) + func newGet<T: Decodable>(_ path: String, parameters: Any? = nil) async -> Result<T, NetworkingError> { + return await handle(.get, path: path, parameters: parameters) } func newPost<T: Decodable>(_ path: String, parameters: [String: Any]) async -> Result<T, NetworkingError> { diff --git a/Sources/Networking/Networking+New.swift b/Sources/Networking/Networking+New.swift index d7a6f0a..adc1a97 100644 --- a/Sources/Networking/Networking+New.swift +++ b/Sources/Networking/Networking+New.swift @@ -30,10 +30,35 @@ extension Networking { } private func createRequest(path: String, requestType: RequestType, parameters: Any?) throws -> URLRequest { - let parameterType: Networking.ParameterType? = parameters != nil ? .json : nil - var request = URLRequest(url: try composedURL(with: path), requestType: requestType, path: path, parameterType: parameterType, responseType: .json, boundary: boundary, authorizationHeaderValue: authorizationHeaderValue, token: token, authorizationHeaderKey: authorizationHeaderKey, headerFields: headerFields) + guard var urlComponents = URLComponents(string: try composedURL(with: path).absoluteString) else { + throw URLError(.badURL) + } + + if requestType == .get, let queryParameters = parameters as? [String: Any] { + urlComponents.queryItems = queryParameters.map { key, value in + URLQueryItem(name: key, value: "\(value)") + } + } + + guard let url = urlComponents.url else { + throw URLError(.badURL) + } - if let parameters = parameters { + let parameterType: Networking.ParameterType? = (requestType == .get || parameters == nil) ? nil : .json + var request = URLRequest( + url: url, + requestType: requestType, + path: path, + parameterType: parameterType, + responseType: .json, + boundary: boundary, + authorizationHeaderValue: authorizationHeaderValue, + token: token, + authorizationHeaderKey: authorizationHeaderKey, + headerFields: headerFields + ) + + if requestType != .get, let parameters = parameters { request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: []) } diff --git a/Tests/NetworkingTests/NewNetworkingTests.swift b/Tests/NetworkingTests/NewNetworkingTests.swift index be4e7dc..ab28ea5 100644 --- a/Tests/NetworkingTests/NewNetworkingTests.swift +++ b/Tests/NetworkingTests/NewNetworkingTests.swift @@ -1,5 +1,6 @@ import Foundation import XCTest +import CoreLocation @testable import Networking class NewNetworkingTests: XCTestCase { @@ -18,6 +19,29 @@ class NewNetworkingTests: XCTestCase { } } + func testNewGETWithParams() async throws { + let networking = Networking(baseURL: baseURL) + + let pickupCoordinate = CLLocationCoordinate2D(latitude: 59.91700978556453, longitude: 10.760668740407757) + let deliveryCoordinate = CLLocationCoordinate2D(latitude: 59.937611066825674, longitude: 10.735343079276985) + + let parameters = [ + "pickup_latitude": pickupCoordinate.latitude, + "pickup_longitude": pickupCoordinate.longitude, + "delivery_latitude": deliveryCoordinate.latitude, + "delivery_longitude": deliveryCoordinate.longitude + ] + + let result: Result<Friend, NetworkingError> = await networking.newGet("/get", parameters: parameters) + + switch result { + case .success(_): + print("Test passed") + case .failure(let error): + print("error \(error)") + } + } + func testNewPOST() async throws { let networking = Networking(baseURL: baseURL)