Skip to content

Commit

Permalink
Merge branch 'main' into task/reorder-client-plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-ricks authored Jul 26, 2023
2 parents 4433ec0 + fbdc4b4 commit acfa1f0
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 10 deletions.
9 changes: 9 additions & 0 deletions Sources/HTTPNetworking/HTTPRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,26 @@ public class HTTPRequest<T: Decodable> {
var request = self.request

do {

try Task.checkCancellation()

// Create the adapted request.
request = try await ZipAdaptor(adaptors).adapt(request, for: dispatcher.session)

try Task.checkCancellation()

// Dispatch the request and wait for a response.
let (data, response) = try await dispatcher.data(for: request)

try Task.checkCancellation()

// Validate the response.
try await ZipValidator(validators)
.validate(response, for: request, with: data)
.get()

try Task.checkCancellation()

// Convert data to the expected type
return try decoder.decode(T.self, from: data)
} catch {
Expand Down
134 changes: 124 additions & 10 deletions Tests/HTTPNetworkingTests/HTTPRequestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,9 @@ class HTTPRequestTests: XCTestCase {
func test_client_withRetrier_runsRetrierOnRequestAndSuccessfullyRetries() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")
var shouldRequestFail = true

Expand All @@ -585,6 +588,8 @@ class HTTPRequestTests: XCTestCase {
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { request in
requestFiredExpectation.fulfill()
})
]),
retriers: [
Expand All @@ -601,17 +606,21 @@ class HTTPRequestTests: XCTestCase {
.request(for: .get, to: url, expecting: [String].self)
.run()

await fulfillment(of: [retryExpectation])
await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

func test_client_withRetrier_runsRetrierOnRequestAndConcedesToFailure() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired once.")
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")

let client = HTTPClient(
dispatcher: .mock(responses: [
url: .failure(expectedError)
url: .failure(expectedError, onRecieveRequest: { _ in
requestFiredExpectation.fulfill()
}),
]),
retriers: [
Retrier { request, session, error in
Expand All @@ -631,12 +640,15 @@ class HTTPRequestTests: XCTestCase {
XCTAssertEqual((error as? URLError)?.code, expectedError.code)
}

await fulfillment(of: [retryExpectation])
await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

func test_client_withMultipleRetriersSuccessfulAndConceding_runsFirstRetrierOnRequestAndNotSecond() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")
var shouldRequestFail = true

Expand All @@ -648,6 +660,8 @@ class HTTPRequestTests: XCTestCase {
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { _ in
requestFiredExpectation.fulfill()
})
]),
retriers: [
Expand All @@ -668,12 +682,15 @@ class HTTPRequestTests: XCTestCase {
.request(for: .get, to: url, expecting: [String].self)
.run()

await fulfillment(of: [retryExpectation])
await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

func test_client_withMultipleRetriersConcedingAndSuccessful_runsBothRetriersOnRequest() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retrierOneExpectation = expectation(description: "Expected retrier one to be called.")
let retrierTwoExpectation = expectation(description: "Expected retrier two to be called.")
var shouldRequestFail = true
Expand All @@ -686,6 +703,8 @@ class HTTPRequestTests: XCTestCase {
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { _ in
requestFiredExpectation.fulfill()
})
]),
retriers: [
Expand All @@ -707,12 +726,15 @@ class HTTPRequestTests: XCTestCase {
.request(for: .get, to: url, expecting: [String].self)
.run()

await fulfillment(of: [retrierOneExpectation, retrierTwoExpectation])
await fulfillment(of: [retrierOneExpectation, retrierTwoExpectation, requestFiredExpectation])
}

func test_request_withRetrier_runsRetrierOnRequestAndSuccessfullyRetries() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")
var shouldRequestFail = true

Expand All @@ -725,6 +747,8 @@ class HTTPRequestTests: XCTestCase {
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { _ in
requestFiredExpectation.fulfill()
})
])
)
Expand All @@ -739,17 +763,21 @@ class HTTPRequestTests: XCTestCase {
})
.run()

await fulfillment(of: [retryExpectation])
await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

func test_request_withRetrier_runsRetrierOnRequestAndConcedesToFailure() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired once.")
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")

let client = HTTPClient(
dispatcher: .mock(responses: [
url: .failure(expectedError)
url: .failure(expectedError, onRecieveRequest: { _ in
requestFiredExpectation.fulfill()
}),
])
)

Expand All @@ -767,12 +795,15 @@ class HTTPRequestTests: XCTestCase {
XCTAssertEqual((error as? URLError)?.code, expectedError.code)
}

await fulfillment(of: [retryExpectation])
await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

func test_request_withMultipleRetriersSuccessfulAndConceding_runsFirstRetrierOnRequestAndNotSecond() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")
var shouldRequestFail = true

Expand All @@ -784,6 +815,8 @@ class HTTPRequestTests: XCTestCase {
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { _ in
requestFiredExpectation.fulfill()
})
])
)
Expand All @@ -802,12 +835,15 @@ class HTTPRequestTests: XCTestCase {
})
.run()

await fulfillment(of: [retryExpectation])
await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

func test_request_withMultipleRetriersConcedingAndSuccessful_runsBothRetriersOnRequest() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retrierOneExpectation = expectation(description: "Expected retrier one to be called.")
let retrierTwoExpectation = expectation(description: "Expected retrier two to be called.")
var shouldRequestFail = true
Expand All @@ -820,6 +856,8 @@ class HTTPRequestTests: XCTestCase {
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { _ in
requestFiredExpectation.fulfill()
})
])
)
Expand All @@ -839,7 +877,7 @@ class HTTPRequestTests: XCTestCase {
})
.run()

await fulfillment(of: [retrierOneExpectation, retrierTwoExpectation])
await fulfillment(of: [retrierOneExpectation, retrierTwoExpectation, requestFiredExpectation])
}

func test_request_withMultipleClientAndRequestRetriers_runsAllRetriersOnRequest() async throws {
Expand Down Expand Up @@ -891,6 +929,82 @@ class HTTPRequestTests: XCTestCase {
])
}

func test_client_withDelayedRetry_doesRetryRequest() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")
var shouldRequestFail = true


let client = HTTPClient(
dispatcher: .mock(responses: [
url: .init(result: {
if shouldRequestFail {
return .failure(expectedError)
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { request in
requestFiredExpectation.fulfill()
})
]),
retriers: [
Retrier { request, session, error in
XCTAssertEqual((error as? URLError)?.code, expectedError.code)
retryExpectation.fulfill()
shouldRequestFail = false
return .retryAfterDelay(.nanoseconds(100))
}
]
)

_ = try await client
.request(for: .get, to: url, expecting: [String].self)
.run()

await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

func test_request_withDelayedRetry_doesRetryRequest() async throws {
let url = createMockUrl()
let expectedError = URLError(.cannotConnectToHost)
let requestFiredExpectation = expectation(description: "Expected request to be fired twice.")
requestFiredExpectation.expectedFulfillmentCount = 2
requestFiredExpectation.assertForOverFulfill = true
let retryExpectation = expectation(description: "Expected retry to be called.")
var shouldRequestFail = true


let client = HTTPClient(
dispatcher: .mock(responses: [
url: .init(result: {
if shouldRequestFail {
return .failure(expectedError)
} else {
return .success((self.data, self.createResponse(for: url, with: 200)))
}
}, onRecieveRequest: { request in
requestFiredExpectation.fulfill()
})
])
)

_ = try await client
.request(for: .get, to: url, expecting: [String].self)
.retry { request, session, error in
XCTAssertEqual((error as? URLError)?.code, expectedError.code)
retryExpectation.fulfill()
shouldRequestFail = false
return .retryAfterDelay(.nanoseconds(100))
}
.run()

await fulfillment(of: [retryExpectation, requestFiredExpectation])
}

// MARK: Task Cancellation Tests

func test_request_whenCancelledBeforeAdaptorsFinish_doesNotProceed() async throws {
Expand Down

0 comments on commit acfa1f0

Please sign in to comment.