diff --git a/Sources/Frisbee/Adapters/DecodableAdapter.swift b/Sources/Frisbee/Adapters/DecodableAdapter.swift index c3b735f..0c0fd3c 100644 --- a/Sources/Frisbee/Adapters/DecodableAdapter.swift +++ b/Sources/Frisbee/Adapters/DecodableAdapter.swift @@ -1,5 +1,5 @@ import Foundation protocol DecodableAdapter { - func decode(_ type: T.Type, from data: Data) throws -> T + func decode(_ type: T.Type, from data: Data?) throws -> T? } diff --git a/Sources/Frisbee/Adapters/DecoderDataAdapter.swift b/Sources/Frisbee/Adapters/DecoderDataAdapter.swift index 0b67d48..81cf9ba 100644 --- a/Sources/Frisbee/Adapters/DecoderDataAdapter.swift +++ b/Sources/Frisbee/Adapters/DecoderDataAdapter.swift @@ -1,9 +1,8 @@ import Foundation final class DecoderDataAdapter: DecodableAdapter { - - func decode(_ type: T.Type, from data: Data) throws -> T { - if let data = data as? T { return data } + func decode(_ type: T.Type, from data: Data?) throws -> T? { + if let data = data as? T? { return data } throw FrisbeeError.invalidEntity } diff --git a/Sources/Frisbee/Adapters/DecoderJSONAdapter.swift b/Sources/Frisbee/Adapters/DecoderJSONAdapter.swift index 798c46d..d97d546 100644 --- a/Sources/Frisbee/Adapters/DecoderJSONAdapter.swift +++ b/Sources/Frisbee/Adapters/DecoderJSONAdapter.swift @@ -1,15 +1,18 @@ import Foundation final class DecoderJSONAdapter: DecodableAdapter { - let decoder: JSONDecoder init(decoder: JSONDecoder = JSONDecoder()) { self.decoder = decoder } - func decode(_ type: T.Type, from data: Data) throws -> T { - return try decoder.decode(type, from: data) + func decode(_ type: T.Type, from data: Data?) throws -> T? { + if let data = data { + return try decoder.decode(type, from: data) + } + + return nil } } diff --git a/Sources/Frisbee/Entities/Result.swift b/Sources/Frisbee/Entities/Result.swift index c5a14f0..ad2a763 100644 --- a/Sources/Frisbee/Entities/Result.swift +++ b/Sources/Frisbee/Entities/Result.swift @@ -1,16 +1,23 @@ public enum Result { - case success(T) + case success(T?, Int?) case fail(FrisbeeError) } extension Result { public var data: T? { - guard case let .success(data) = self else { + guard case .success(let data, _) = self else { return nil } return data } + public var httpStatusCode: Int? { + guard case .success(_, let httpStatusCode) = self else { + return nil + } + return httpStatusCode + } + public var error: FrisbeeError? { guard case let .fail(error) = self else { return nil diff --git a/Sources/Frisbee/Interactors/DataTaskRunner.swift b/Sources/Frisbee/Interactors/DataTaskRunner.swift index e85e031..09b5f6d 100644 --- a/Sources/Frisbee/Interactors/DataTaskRunner.swift +++ b/Sources/Frisbee/Interactors/DataTaskRunner.swift @@ -4,8 +4,8 @@ final class DataTaskRunner { static func run(with urlSession: URLSession, request: URLRequest, onComplete: @escaping OnComplete) -> Cancellable { - let task = urlSession.dataTask(with: request) { data, _, error in - onComplete(ResultGeneratorFactory.make().generate(data: data, error: error)) + let task = urlSession.dataTask(with: request) { data, urlResponse, error in + onComplete(ResultGeneratorFactory.make().generate(data: data, urlResponse: urlResponse, error: error)) } task.resume() return URLSessionTaskAdapter(task: task) diff --git a/Sources/Frisbee/Interactors/ResultGenerator.swift b/Sources/Frisbee/Interactors/ResultGenerator.swift index 625df9f..a09300f 100644 --- a/Sources/Frisbee/Interactors/ResultGenerator.swift +++ b/Sources/Frisbee/Interactors/ResultGenerator.swift @@ -8,8 +8,8 @@ final class ResultGenerator { self.decoder = decoder } - func generate(data: Data?, error: Error?) -> Result { - guard let data = data else { + func generate(data: Data?, urlResponse: URLResponse?, error: Error?) -> Result { + guard let urlResponse = urlResponse else { switch error { case .some(let error): return .fail(FrisbeeError(error)) case .none: return .fail(FrisbeeError.noData) @@ -18,7 +18,11 @@ final class ResultGenerator { do { let entityDecoded = try decoder.decode(T.self, from: data) - return .success(entityDecoded) + + if urlResponse is HTTPURLResponse { + return .success(entityDecoded, (urlResponse as! HTTPURLResponse).statusCode) + } + return .success(entityDecoded, nil) } catch { return .fail(.noData) } } diff --git a/Tests/FrisbeeTests/Adapters/DecoderDataAdapterTests.swift b/Tests/FrisbeeTests/Adapters/DecoderDataAdapterTests.swift index 58c71e5..9ad1dde 100644 --- a/Tests/FrisbeeTests/Adapters/DecoderDataAdapterTests.swift +++ b/Tests/FrisbeeTests/Adapters/DecoderDataAdapterTests.swift @@ -8,7 +8,13 @@ final class DecoderDataAdapterTests: XCTestCase { let decodedData = try DecoderDataAdapter().decode(Data.self, from: data) - XCTAssertEqual(decodedData.count, data.count) + XCTAssertEqual(decodedData!.count, data.count) + } + + func testDecodeWhenEncodableGenericIsNilThenDecodeDataIsNil() throws { + let decodedData = try DecoderDataAdapter().decode(Data.self, from: nil) + + XCTAssertNil(decodedData) } func testDecodeWhenNotDataEntityThenThrowsError() throws { diff --git a/Tests/FrisbeeTests/Entities/ResultTests.swift b/Tests/FrisbeeTests/Entities/ResultTests.swift index a0ec734..4a77874 100644 --- a/Tests/FrisbeeTests/Entities/ResultTests.swift +++ b/Tests/FrisbeeTests/Entities/ResultTests.swift @@ -5,7 +5,7 @@ import XCTest class ResultTests: XCTestCase { func testEquatableEnumCasesWhenAssociatedValueAreEquatableThenShouldBeEqual() { - let success = Result.success(SomeEquatableEntity()) + let success = Result.success(SomeEquatableEntity(), nil) let fail = Result.fail(FrisbeeError.invalidEntity) XCTAssertEqual(success.data, success.data) @@ -18,7 +18,7 @@ class ResultTests: XCTestCase { } func testEquatableEnumCasesWhenAssociatedValueArentEquatableThenShoudBeNotEqual() { - let success = Result.success(SomeEntity()) + let success = Result.success(SomeEntity(), nil) let fail = Result.fail(FrisbeeError.invalidEntity) XCTAssertNotEqual(success, success) @@ -28,7 +28,7 @@ class ResultTests: XCTestCase { } func testEqualityOperatorWhenEquatableEntityThenShoudeBeEqual() { - let success = Result.success(SomeEquatableEntity()) + let success = Result.success(SomeEquatableEntity(), nil) let fail = Result.fail(FrisbeeError.invalidEntity) XCTAssertTrue(success == success) diff --git a/Tests/FrisbeeTests/Interactors/ResultGeneratorTests.swift b/Tests/FrisbeeTests/Interactors/ResultGeneratorTests.swift index 949589a..2f5f07a 100644 --- a/Tests/FrisbeeTests/Interactors/ResultGeneratorTests.swift +++ b/Tests/FrisbeeTests/Interactors/ResultGeneratorTests.swift @@ -5,49 +5,83 @@ final class ResultGeneratorTests: XCTestCase { private let someError = NSError(domain: "Some error", code: 33, userInfo: nil) private let fakeString = "Fake Fake" + private let fakeUrl = URL(string: "www.domain.io")! - func testGenerateResultWhenEncoderTrhowAnerrorThenGenerateFailResult() { + func testGenerateResultWhenEncoderThrowAnErrorThenGenerateFailResult() { let data = try? JSONEncoder().encode(Fake(fake: fakeString)) let resultGenerator = ResultGenerator(decoder: DecoderThrowErrorFakeAdapter()) + let fakeUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 200, httpVersion: nil, headerFields: nil)! - let result = resultGenerator.generate(data: data, error: nil) + let result = resultGenerator.generate(data: data, urlResponse: fakeUrlResponse, error: nil) XCTAssertEqual(result.error, .noData) } func testGenerateResultWhenInvalidDataThenGenerateSuccessResult() { - let noDataError = FrisbeeError.noData let data = try? JSONEncoder().encode(Data()) let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() + let noContentUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 204, httpVersion: nil, headerFields: nil)! - let result = resultGenerator.generate(data: data, error: nil) + let result = resultGenerator.generate(data: data, urlResponse: noContentUrlResponse, error: nil) - XCTAssertEqual(result.error, noDataError) + XCTAssertNil(result.data) } func testGenerateResultWhenHasDataThenGenerateSuccessResult() { let data = try? JSONEncoder().encode(Fake(fake: fakeString)) let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() + let fakeUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 201, httpVersion: nil, headerFields: nil)! - let result = resultGenerator.generate(data: data, error: nil) + let result = resultGenerator.generate(data: data, urlResponse: fakeUrlResponse, error: nil) XCTAssertEqual(result.data?.fake, fakeString) + XCTAssertEqual(result.httpStatusCode, fakeUrlResponse.statusCode) } func testGenerateResultWhenHasDataThenResultFailErrorIsNil() { let data = try? JSONEncoder().encode(Fake(fake: fakeString)) let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() + let fakeUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 201, httpVersion: nil, headerFields: nil)! + + let result = resultGenerator.generate(data: data, urlResponse: fakeUrlResponse, error: nil) + + XCTAssertNil(result.error) + } - let result = resultGenerator.generate(data: data, error: nil) + func testGenerateResultWhenHasNoContentUrlResponseWithNilDataThenErrorIsNil() { + let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() + let noContentUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 204, httpVersion: nil, headerFields: nil)! + + let result = resultGenerator.generate(data: nil, urlResponse: noContentUrlResponse, error: nil) XCTAssertNil(result.error) } - func testGenerateResultWhenHasNilDataThenGenerateNoDataErrorResult() { + func testGenerateResultWhenHasNoContentUrlResponseWithDataThenGenerateSuccessResult() { + let data = try? JSONEncoder().encode(Fake(fake: fakeString)) + let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() + let noContentUrlResponse = HTTPURLResponse(url: fakeUrl, statusCode: 204, httpVersion: nil, headerFields: nil)! + + let result = resultGenerator.generate(data: data, urlResponse: noContentUrlResponse, error: nil) + + XCTAssertEqual(result.data?.fake, fakeString) + } + + func testGenerateResultWhenHasNilDataAndNilUrlResponseThenGenerateNoDataErrorResult() { + let noDataError = Result.fail(FrisbeeError.noData) + let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() + + let result = resultGenerator.generate(data: nil, urlResponse: nil, error: nil) + + XCTAssertEqual(result, noDataError) + } + + func testGenerateResultWhenHasNilUrlResponseThenGenerateNoDataErrorResult() { + let data = try? JSONEncoder().encode(Fake(fake: fakeString)) let noDataError = Result.fail(FrisbeeError.noData) let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() - let result = resultGenerator.generate(data: nil, error: nil) + let result = resultGenerator.generate(data: data, urlResponse: nil, error: nil) XCTAssertEqual(result, noDataError) } @@ -55,7 +89,7 @@ final class ResultGeneratorTests: XCTestCase { func testGenerateResultWhenHasNilDataThenResultSuccessDataIsNil() { let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() - let result = resultGenerator.generate(data: nil, error: nil) + let result = resultGenerator.generate(data: nil, urlResponse: nil, error: nil) XCTAssertNil(result.data) } @@ -64,7 +98,7 @@ final class ResultGeneratorTests: XCTestCase { let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() let noDataError = FrisbeeError.other(localizedDescription: someError.localizedDescription) - let result = resultGenerator.generate(data: nil, error: someError) + let result = resultGenerator.generate(data: nil, urlResponse: nil, error: someError) XCTAssertEqual(result.error, noDataError) } @@ -72,20 +106,26 @@ final class ResultGeneratorTests: XCTestCase { func testGenerateResultWhenHasErrorThenResultSuccessDataIsNil() { let resultGenerator: ResultGenerator = ResultGeneratorFactory.make() - let result = resultGenerator.generate(data: nil, error: someError) + let result = resultGenerator.generate(data: nil, urlResponse: nil, error: someError) XCTAssertNil(result.data) } static var allTests = [ - ("testGenerateResultWhenEncoderTrhowAnerrorThenGenerateFailResult", - testGenerateResultWhenEncoderTrhowAnerrorThenGenerateFailResult), + ("testGenerateResultWhenEncoderThrowAnErrorThenGenerateFailResult", + testGenerateResultWhenEncoderThrowAnErrorThenGenerateFailResult), ("testGenerateResultWhenHasDataThenGenerateSuccessResult", testGenerateResultWhenHasDataThenGenerateSuccessResult), ("testGenerateResultWhenHasDataThenResultFailErrorIsNil", testGenerateResultWhenHasDataThenResultFailErrorIsNil), - ("testGenerateResultWhenHasNilDataThenGenerateNoDataErrorResult", - testGenerateResultWhenHasNilDataThenGenerateNoDataErrorResult), + ("testGenerateResultWhenHasNilUrlResponseThenGenerateNoDataErrorResult", + testGenerateResultWhenHasNilUrlResponseThenGenerateNoDataErrorResult), + ("testGenerateResultWhenHasNilDataAndNilUrlResponseThenGenerateNoDataErrorResult", + testGenerateResultWhenHasNilDataAndNilUrlResponseThenGenerateNoDataErrorResult), + ("testGenerateResultWhenHasNoContentUrlResponseWithNilDataThenErrorIsNil", + testGenerateResultWhenHasNoContentUrlResponseWithNilDataThenErrorIsNil), + ("testGenerateResultWhenHasNoContentUrlResponseWithDataThenGenerateSuccessResult", + testGenerateResultWhenHasNoContentUrlResponseWithDataThenGenerateSuccessResult), ("testGenerateResultWhenHasNilDataThenResultSuccessDataIsNil", testGenerateResultWhenHasNilDataThenResultSuccessDataIsNil), ("testGenerateResultWhenHasErrorThenGenerateErrorResult", diff --git a/Tests/FrisbeeTests/Requestables/NetworkGetTests.swift b/Tests/FrisbeeTests/Requestables/NetworkGetTests.swift index 2532391..496f5e4 100644 --- a/Tests/FrisbeeTests/Requestables/NetworkGetTests.swift +++ b/Tests/FrisbeeTests/Requestables/NetworkGetTests.swift @@ -50,6 +50,17 @@ final class NetworkGetTests: XCTestCase { XCTAssertNil(generatedResult.error) } + func testGetWhenValidURLWithoutBodyThenGenerateSuccessResult() { + let session = MockURLSession(results: [.success(nil, URLResponse())]) + let getter = NetworkGet(urlSession: session) + var generatedResult: Result! + + getter.get(url: validUrlString) { generatedResult = $0 } + + XCTAssertNil(generatedResult.data) + XCTAssertNil(generatedResult.error) + } + func testGetWhenInvalidURLThenGenerateFailResult() { let session = MockURLSession(results: []) let getter = NetworkGet(urlSession: session) diff --git a/Tests/FrisbeeTests/Requestables/NetworkPostTests.swift b/Tests/FrisbeeTests/Requestables/NetworkPostTests.swift index 76aadaa..70103bc 100644 --- a/Tests/FrisbeeTests/Requestables/NetworkPostTests.swift +++ b/Tests/FrisbeeTests/Requestables/NetworkPostTests.swift @@ -33,6 +33,17 @@ final class NetworkPostTests: XCTestCase { XCTAssertNil(generatedResult.error) } + func testPostWhenValidURLWithoutBodyThenGenerateSuccessResult() { + let session = MockURLSession(results: [.success(nil, URLResponse())]) + let networkPost = NetworkPost(urlSession: session) + var generatedResult: Result! + + networkPost.post(url: validUrlString) { generatedResult = $0 } + + XCTAssertNil(generatedResult.data) + XCTAssertNil(generatedResult.error) + } + func testPostWhenValidURLWithBodyThenGenerateSuccessResult() { let session = MockURLSession(results: [.success(Empty.data, URLResponse())]) let networkPost = NetworkPost(urlSession: session) diff --git a/Tests/FrisbeeTests/Requestables/NetworkPutTests.swift b/Tests/FrisbeeTests/Requestables/NetworkPutTests.swift index 0b1cc39..9cd52be 100644 --- a/Tests/FrisbeeTests/Requestables/NetworkPutTests.swift +++ b/Tests/FrisbeeTests/Requestables/NetworkPutTests.swift @@ -45,6 +45,18 @@ class NetworkPutTests: XCTestCase { XCTAssertNil(generatedResult.error) } + func testPutWhenValidURLWithoutBodyThenGenerateSuccessResult() { + let session = MockURLSession(results: [.success(nil, URLResponse())]) + let networkPut = NetworkPut(urlSession: session) + let body = Empty() + var generatedResult: Result! + + networkPut.put(url: validUrlString, body: body) { generatedResult = $0 } + + XCTAssertNil(generatedResult.data) + XCTAssertNil(generatedResult.error) + } + func testPutWhenInvalidURLThenGenerateFailResult() { let session = MockURLSession(results: []) let networkPut = NetworkPut(urlSession: session) diff --git a/Tests/FrisbeeTests/Support/Doubles/DecoderThrowErrorFakeAdapter.swift b/Tests/FrisbeeTests/Support/Doubles/DecoderThrowErrorFakeAdapter.swift index cc06c41..bea4cec 100644 --- a/Tests/FrisbeeTests/Support/Doubles/DecoderThrowErrorFakeAdapter.swift +++ b/Tests/FrisbeeTests/Support/Doubles/DecoderThrowErrorFakeAdapter.swift @@ -4,7 +4,7 @@ import Foundation class DecoderThrowErrorFakeAdapter: DecodableAdapter { private let error = FrisbeeError.invalidEntity - func decode(_ type: T.Type, from data: Data) throws -> T { + func decode(_ type: T.Type, from data: Data?) throws -> T? { throw error } diff --git a/Tests/FrisbeeTests/Support/Doubles/MockURLSession.swift b/Tests/FrisbeeTests/Support/Doubles/MockURLSession.swift index e3be3c2..034fa85 100644 --- a/Tests/FrisbeeTests/Support/Doubles/MockURLSession.swift +++ b/Tests/FrisbeeTests/Support/Doubles/MockURLSession.swift @@ -30,7 +30,7 @@ class MockURLSession: URLSession { class MockDataTask: URLSessionDataTask { enum Result { - case success(Data, URLResponse), error(Error) + case success(Data?, URLResponse), error(Error) } let result: Result