Skip to content

Commit

Permalink
fix: Cache write interceptor should gracefully handle missing cache r…
Browse files Browse the repository at this point in the history
…ecords (#439)
  • Loading branch information
calvincestari authored Jul 24, 2024
1 parent b541eee commit 9bde5f1
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 15 deletions.
50 changes: 50 additions & 0 deletions Tests/ApolloTests/RequestChainTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,56 @@ class RequestChainTests: XCTestCase {
}
}

func test__error__givenGraphqlError_withoutData_shouldReturnError() {
// given
let client = MockURLSessionClient(
response: .mock(
url: TestURL.mockServer.url,
statusCode: 200,
httpVersion: nil,
headerFields: nil
),
data: """
{
"errors": [{
"message": "Bad request, could not start execution!"
}]
}
""".data(using: .utf8)
)

let interceptorProvider = DefaultInterceptorProvider(client: client, store: ApolloStore())
let interceptors = interceptorProvider.interceptors(for: MockQuery.mock())
let requestChain = InterceptorRequestChain(interceptors: interceptors)

let expectation = expectation(description: "Response received")

let request = JSONRequest(
operation: MockQuery<Hero>(),
graphQLEndpoint: TestURL.mockServer.url,
clientName: "test-client",
clientVersion: "test-client-version"
)

// when + then
requestChain.kickoff(request: request) { result in
defer {
expectation.fulfill()
}

switch (result) {
case let .success(data):
XCTAssertEqual(data.errors, [
GraphQLError("Bad request, could not start execution!")
])
case let .failure(error):
XCTFail("Unexpected failure result - \(error)")
}
}

wait(for: [expectation], timeout: 1)
}

// MARK: Multipart request tests

struct RequestTrapInterceptor: ApolloInterceptor {
Expand Down
24 changes: 9 additions & 15 deletions apollo-ios/Sources/Apollo/CacheWriteInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,12 @@ import ApolloAPI
public struct CacheWriteInterceptor: ApolloInterceptor {

public enum CacheWriteError: Error, LocalizedError {
@available(*, deprecated, message: "Will be removed in a future version.")
case noResponseToParse

case missingCacheRecords

public var errorDescription: String? {
switch self {
case .noResponseToParse:
return "The Cache Write Interceptor was called before a response was received to be parsed. Double-check the order of your interceptors."
case .missingCacheRecords:
return "The Cache Write Interceptor cannot find any cache records. Double-check the order of your interceptors."
}
}
}
Expand All @@ -37,7 +32,11 @@ public struct CacheWriteInterceptor: ApolloInterceptor {
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, any Error>) -> Void) {


guard !chain.isCancelled else {
return
}

guard request.cachePolicy != .fetchIgnoringCacheCompletely else {
// If we're ignoring the cache completely, we're not writing to it.
chain.proceedAsync(
Expand All @@ -49,25 +48,20 @@ public struct CacheWriteInterceptor: ApolloInterceptor {
return
}

guard
let createdResponse = response,
let cacheRecords = createdResponse.cacheRecords
else {
guard let createdResponse = response else {
chain.handleErrorAsync(
CacheWriteError.missingCacheRecords,
CacheWriteError.noResponseToParse,
request: request,
response: response,
completion: completion
)
return
}

guard !chain.isCancelled else {
return
if let cacheRecords = createdResponse.cacheRecords {
self.store.publish(records: cacheRecords, identifier: request.contextIdentifier)
}

self.store.publish(records: cacheRecords, identifier: request.contextIdentifier)

chain.proceedAsync(
request: request,
response: createdResponse,
Expand Down

0 comments on commit 9bde5f1

Please sign in to comment.