Files
2022-08-11 18:06:20 +01:00

279 lines
9.4 KiB
Swift

// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
@testable import XCRemoteCache
import XCTest
class NetworkClientImplTests: XCTestCase {
typealias Completion<R> = (Result<R, NetworkClientError>) -> Void
private var responses: [URL: URLProtocolStub.Response] {
get {
URLProtocolStub.responses
}
set {
URLProtocolStub.responses = newValue
}
}
private var requests: [URLRequest] {
get {
URLProtocolStub.requests
}
set {
URLProtocolStub.requests = newValue
}
}
private let successStatus = 200
private let failureStatus = 400
private var url: URL!
private var successResponse: URLResponse!
private var failureResponse: URLResponse!
private var session: URLSession!
private let fileURL = URL(fileURLWithPath: "/sample")
private var fileManager: FileManager!
private var client: NetworkClientImpl!
override func setUpWithError() throws {
try super.setUpWithError()
responses = [:]
requests = []
url = try URL(string: "http://example.com").unwrap()
successResponse = try HTTPURLResponse(
url: url,
statusCode: successStatus,
httpVersion: nil,
headerFields: nil
).unwrap()
failureResponse = try HTTPURLResponse(
url: url,
statusCode: failureStatus,
httpVersion: nil,
headerFields: nil
).unwrap()
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [URLProtocolStub.self]
session = URLSession(configuration: configuration)
fileManager = FileManager.default
client = NetworkClientImpl(
session: session,
retries: 0,
retryDelay: 0,
fileManager: fileManager,
awsV4Signature: nil
)
}
override func tearDown() {
url = nil
successResponse = nil
failureResponse = nil
client = nil
session = nil
fileManager = nil
requests = []
responses = [:]
super.tearDown()
}
func waitForResponse<R>(_ action: (@escaping Completion<R>) -> Void, timeout: TimeInterval = 0.1) throws -> Result<R, NetworkClientError> {
let responseExpectation = expectation(description: "RequestResponse")
var receivedResponse: Result<R, NetworkClientError>?
action { response in
receivedResponse = response
responseExpectation.fulfill()
}
waitForExpectations(timeout: timeout)
return try receivedResponse.unwrap()
}
func testFetch400CompletesWithFailure() throws {
responses[url] = .success(failureResponse, Data())
let response = try waitForResponse { client.fetch(url, completion: $0) }
guard case .failure(.unsuccessfulResponse(failureStatus)) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testFetch200CompletesWithSuccess() throws {
responses[url] = .success(successResponse, Data())
let response = try waitForResponse { client.fetch(url, completion: $0) }
guard case .success(Data()) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testUpload200CompletesWithSuccess() throws {
let fileURL = URL(fileURLWithPath: "/sample")
responses[url] = .success(successResponse, Data())
let response = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
guard case .success(()) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testUpload400CompletesWithFailure() throws {
responses[url] = .success(failureResponse, Data())
let response = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
guard case .failure(.unsuccessfulResponse(failureStatus)) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testUploadFilureWith400Retries() throws {
client = NetworkClientImpl(
session: session,
retries: 2,
retryDelay: 0,
fileManager: fileManager,
awsV4Signature: nil
)
responses[url] = .success(failureResponse, Data())
_ = try waitForResponse({ client.upload(fileURL, as: url, completion: $0) }, timeout: 0.5)
XCTAssertEqual(
requests.map(\.url),
Array(repeating: url, count: 3),
"Expected 3 requests (original + 2 retries)"
)
}
func testUploadSuccessDoesntRetry() throws {
client = NetworkClientImpl(
session: session,
retries: 2,
retryDelay: 0,
fileManager: fileManager,
awsV4Signature: nil
)
responses[url] = .success(successResponse, Data())
_ = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
XCTAssertEqual(requests.map(\.url), [url], "Expected 1 request - original only")
}
func testDownloadFilureWith400Retries() throws {
client = NetworkClientImpl(
session: session,
retries: 2,
retryDelay: 0,
fileManager: fileManager,
awsV4Signature: nil
)
responses[url] = nil
_ = try waitForResponse({ client.download(url, to: fileURL, completion: $0) }, timeout: 0.5)
XCTAssertEqual(
requests.map(\.url),
Array(repeating: url, count: 3),
"Expected 3 requests (original + 2 retries)"
)
}
func testDownloadSuccessDoesntRetry() throws {
client = NetworkClientImpl(
session: session,
retries: 2,
retryDelay: 0,
fileManager: fileManager,
awsV4Signature: nil
)
responses[url] = .success(successResponse, Data())
_ = try waitForResponse { client.download(url, to: fileURL, completion: $0) }
XCTAssertEqual(requests.map(\.url), [url], "Expected 1 request - original only")
}
func testFileExits400CompletesWithFalse() throws {
responses[url] = .success(failureResponse, Data())
let response = try waitForResponse { client.fileExists(url, completion: $0) }
guard case .success(false) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testFileExits200CompletesWithTrue() throws {
responses[url] = .success(successResponse, Data())
let response = try waitForResponse { client.fileExists(url, completion: $0) }
guard case .success(true) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testFileExitsErrorCompletesWithFailure() throws {
let response = try waitForResponse { client.fileExists(url, completion: $0) }
guard case .failure(.other) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testDownloadTimeoutReportsTimeoutError() throws {
responses[url] = .timeout
let response = try waitForResponse { client.fileExists(url, completion: $0) }
guard case .failure(.timeout) = response else {
XCTFail("Unexpected response \(response).")
return
}
}
func testAWSV4SignatureHeader() throws {
let signature = AWSV4Signature(
secretKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
accessKey: "AKIDEXAMPLE",
securityToken: "IQoJb3JpZ2luX2VjENv//////////wEaCXVzLWVhc3Q+bsHwqnovXtl/1JVe61XHMnAw3AIXwOAOxqMvhw==",
region: "us-east-1",
service: "iam",
date: Date(timeIntervalSince1970: 1_440_938_160)
)
client = NetworkClientImpl(
session: session,
retries: 0,
retryDelay: 0,
fileManager: fileManager,
awsV4Signature: signature
)
responses[url] = .success(successResponse, Data())
_ = try waitForResponse { client.fetch(url, completion: $0) }
XCTAssertEqual(
"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, " +
"SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, " +
"Signature=e5578464567fb97fd26e871702e4ec4ff7d61cb87eb72a40d22b80e12da30c34",
try requests[0].allHTTPHeaderFields?["Authorization"].unwrap()
)
}
}