Skip to content

Commit

Permalink
[feat]: Rename MockResponseProvider to StoredResponseProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
johnkodes committed Jan 24, 2024
1 parent 8995951 commit ce5394b
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class SampleAuthorizationManager: AuthorizationManaging {
/// We use mock data to simulate real API requests here
private let apiManager: APIManager = {
APIManager(
responseProvider: MockResponseProvider(with: Bundle.main, sessionId: "2023-01-31T15:08:08Z"),
responseProvider: StoredResponseProvider(with: Bundle.main, sessionId: "2023-01-31T15:08:08Z"),
requestAdapters: [LoggingInterceptor.shared],
responseProcessors: [
LoggingInterceptor.shared,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class AuthorizationViewModel: ObservableObject {
#endif

return APIManager(
responseProvider: MockResponseProvider(with: Bundle.main, sessionId: "2023-01-31T15:08:08Z"),
responseProvider: StoredResponseProvider(with: Bundle.main, sessionId: "2023-01-31T15:08:08Z"),
requestAdapters: [
LoggingInterceptor.shared,
authorizationInterceptor
Expand Down
79 changes: 79 additions & 0 deletions Sources/Networking/Core/StoredResponseProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// StoredResponseProvider.swift
//
//
// Created by Matej Molnár on 04.01.2023.
//

import Foundation

// necessary for NSDataAsset import
#if os(macOS)
import AppKit
#else
import UIKit
#endif

/// A response provider which creates responses for requests from corresponding data files stored in Assets.
open class StoredResponseProvider: ResponseProviding {
private let bundle: Bundle
private let sessionId: String
private let requestCounter = Counter()
private lazy var decoder = JSONDecoder()

/// Creates MockResponseProvider instance.
/// - Parameters:
/// - bundle: A bundle which includes the assets file.
/// - sessionId: An ID of a session, which data should be read.
public init(with bundle: Bundle, sessionId: String) {
self.bundle = bundle
self.sessionId = sessionId
}

/// Creates a ``Response`` for a given `URLRequest` based on data from a corresponding file stored in Assets.
/// - Parameter request: URL request.
public func response(for request: URLRequest) async throws -> Response {
guard let model = try? await loadModel(for: request) else {
throw NetworkError.underlying(error: StoredResponseProviderError.unableToLoadAssetData)
}

guard
let statusCode = model.statusCode,
let url = request.url,
let httpResponse = HTTPURLResponse(
url: url,
statusCode: statusCode,
httpVersion: nil,
headerFields: model.responseHeaders
)
else {
throw NetworkError.underlying(error: StoredResponseProviderError.unableToConstructResponse)
}

return Response(model.responseBody ?? Data(), httpResponse)
}
}

// MARK: Private helper functions

private extension StoredResponseProvider {
/// Loads a corresponding file from Assets for a given ``URLRequest`` and decodes the data to `EndpointRequestStorageModel`.
func loadModel(for request: URLRequest) async throws -> EndpointRequestStorageModel? {
// counting from 0, check storage request processing
let count = await requestCounter.count(for: request.identifier)

if let data = NSDataAsset(name: "\(sessionId)_\(request.identifier)_\(count)", bundle: bundle)?.data {
// store info about next indexed api call
await requestCounter.increment(for: request.identifier)
return try decoder.decode(EndpointRequestStorageModel.self, from: data)
}

// return previous response, if no more stored indexed api calls
// swiftlint:disable:next empty_count
if count > 0, let data = NSDataAsset(name: "\(sessionId)_\(request.identifier)_\(count - 1)", bundle: bundle)?.data {
return try decoder.decode(EndpointRequestStorageModel.self, from: data)
}

return nil
}
}
16 changes: 16 additions & 0 deletions Sources/Networking/Misc/StoredResponseProviderError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// StoredResponseProvider.swift
//
//
// Created by Matej Molnár on 04.01.2023.
//

import Foundation

/// An error that occurs during loading a ``Response`` from assets by `StoredResponseProvider`.
enum StoredResponseProviderError: Error {
/// An indication that there was a problem with loading or decoding data from assets.
case unableToLoadAssetData
/// An indication that it was not possible to construct a `Response` from the loaded data.
case unableToConstructResponse
}
1 change: 0 additions & 1 deletion Tests/NetworkingTests/Mocks/MockFileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// Created by Jan Kodeš on 23.01.2024.
//

import Foundation
import Foundation
import XCTest

Expand Down
104 changes: 104 additions & 0 deletions Tests/NetworkingTests/StoredResponseProviderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// StoredResponseProviderTests.swift
//
//
// Created by Matej Molnár on 05.01.2023.
//

@testable import Networking
import XCTest

final class StoredResponseProviderTests: XCTestCase {
// swiftlint:disable:next force_unwrapping
private lazy var mockUrlRequest = URLRequest(url: URL(string: "https://reqres.in/api/users?page=2")!)
private let mockSessionId = "2023-01-04T16:15:29Z"

private let mockHeaderFields = [
"Server": "cloudflare",
"Etag": "W/\"406-ut0vzoCuidvyMf8arZpMpJ6ZRDw\"",
"x-powered-by": "Express",
"nel": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}",
"Content-Encoding": "br",
"Vary": "Accept-Encoding",
// swiftlint:disable:next line_length
"report-to": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=5XGHUrnfYDsl7guBAx0nFk7LTbUgOLjp5%2BGMkSPetC5OrW6fKlUc1NBBtOKHKe9yWrcbXkF4TQe8jsv1c4KggYW1q4pYf5G2rQvA8XACg1znl6MbWiNj1w2wOg%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}",
"Content-Type": "application/json; charset=utf-8",
"cf-cache-status": "HIT",
"Cache-Control": "max-age=14400",
"Access-Control-Allow-Origin": "*",
"cf-ray": "784545f34d2f27bc-PRG",
"Date": "Wed, 04 Jan 2023 16:15:29 GMT",
"Via": "1.1 vegur",
"Age": "6306"
]

func testLoadingData() async throws {
let storedResponseProvider = StoredResponseProvider(with: Bundle.module, sessionId: mockSessionId)

// call request multiple times, 6 testing data files
// test reading correct file
for index in 0...10 {
let response = try await storedResponseProvider.response(for: mockUrlRequest)

XCTAssert(response.response is HTTPURLResponse)

guard let httpResponse = response.response as? HTTPURLResponse else {
XCTAssert(false, "Wrong response type")
return
}

guard let headerFields = httpResponse.allHeaderFields as? [String: String] else {
XCTAssert(false, "Wrong response header fields type")
return
}

XCTAssertEqual(headerFields, mockHeaderFields)

switch index {
case 3:
XCTAssertEqual(httpResponse.statusCode, 200)
XCTAssertEqual(response.data.count, 0)
case 4:
XCTAssertEqual(httpResponse.statusCode, 400)
XCTAssertEqual(response.data.count, 0)
default:
XCTAssertEqual(httpResponse.statusCode, 200)
XCTAssertEqual(response.data.count, 1030)
}
}
}

func testUnableToLoadAssetError() async {
let storedResponseProvider = StoredResponseProvider(with: Bundle.module, sessionId: "NonexistentSessionId")

do {
_ = try await storedResponseProvider.response(for: mockUrlRequest)
XCTAssert(false, "function didn't throw an error even though it should have")
} catch {
var correctError = false
if case NetworkError.underlying(error: StoredResponseProviderError.unableToLoadAssetData) = error {
correctError = true
}
XCTAssert(correctError, "function threw an incorrect error")
}
}

func testUnableToConstructResponseError() async {
let storedResponseProvider = StoredResponseProvider(with: Bundle.module, sessionId: "2023-01-04T16:15:29Z(corrupted)")

do {
_ = try await storedResponseProvider.response(for: mockUrlRequest)
XCTAssert(false, "function didn't throw an error even though it should have")
} catch {
var correctError = false
if case NetworkError.underlying(error: StoredResponseProviderError.unableToConstructResponse) = error {
correctError = true
}
XCTAssert(correctError, "function threw an incorrect error")
}
}

static var allTests = [
("testLoadingData", testLoadingData)
]
}

0 comments on commit ce5394b

Please sign in to comment.