| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- //
- // TestHelpers.swift
- //
- // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- import Alamofire
- import Foundation
- extension String {
- static let invalidURL = "invalid"
- static let nonexistentDomain = "https://nonexistent-domain.org"
- }
- extension URL {
- static let nonexistentDomain = URL(string: .nonexistentDomain)!
- }
- struct Endpoint {
- enum Scheme: String {
- case http, https
- var port: Int {
- switch self {
- case .http: 80
- case .https: 443
- }
- }
- }
- enum Host: String {
- case localhost = "127.0.0.1"
- case httpBin = "httpbin.org"
- func port(for scheme: Scheme) -> Int {
- switch self {
- case .localhost: 8080
- case .httpBin: scheme.port
- }
- }
- }
- enum Path {
- case basicAuth(username: String, password: String)
- case bytes(count: Int)
- case cache
- case chunked(count: Int)
- case compression(Compression)
- case delay(interval: Int)
- case digestAuth(qop: String = "auth", username: String, password: String)
- case download(count: Int)
- case hiddenBasicAuth(username: String, password: String)
- case image(Image)
- case ip
- case method(HTTPMethod)
- case payloads(count: Int)
- case redirect(count: Int)
- case redirectTo
- case responseHeaders
- case status(Int)
- case stream(count: Int)
- case upload
- case websocket
- case websocketCount(Int)
- case websocketEcho
- case websocketPingCount(Int)
- case xml
- var string: String {
- switch self {
- case let .basicAuth(username: username, password: password):
- "/basic-auth/\(username)/\(password)"
- case let .bytes(count):
- "/bytes/\(count)"
- case .cache:
- "/cache"
- case let .chunked(count):
- "/chunked/\(count)"
- case let .compression(compression):
- "/\(compression.rawValue)"
- case let .delay(interval):
- "/delay/\(interval)"
- case let .digestAuth(qop, username, password):
- "/digest-auth/\(qop)/\(username)/\(password)"
- case let .download(count):
- "/download/\(count)"
- case let .hiddenBasicAuth(username, password):
- "/hidden-basic-auth/\(username)/\(password)"
- case let .image(type):
- "/image/\(type.rawValue)"
- case .ip:
- "/ip"
- case let .method(method):
- "/\(method.rawValue.lowercased())"
- case let .payloads(count):
- "/payloads/\(count)"
- case let .redirect(count):
- "/redirect/\(count)"
- case .redirectTo:
- "/redirect-to"
- case .responseHeaders:
- "/response-headers"
- case let .status(code):
- "/status/\(code)"
- case let .stream(count):
- "/stream/\(count)"
- case .upload:
- "/upload"
- case .websocket:
- "/websocket"
- case let .websocketCount(count):
- "/websocket/payloads/\(count)"
- case .websocketEcho:
- "/websocket/echo"
- case let .websocketPingCount(count):
- "/websocket/ping/\(count)"
- case .xml:
- "/xml"
- }
- }
- }
- enum Image: String {
- case jpeg
- }
- enum Compression: String {
- case brotli, gzip, deflate
- }
- static var get: Endpoint { method(.get) }
- static func basicAuth(forUser user: String = "user", password: String = "password") -> Endpoint {
- Endpoint(path: .basicAuth(username: user, password: password))
- }
- static func bytes(_ count: Int) -> Endpoint {
- Endpoint(path: .bytes(count: count))
- }
- static let cache: Endpoint = .init(path: .cache)
- static func chunked(_ count: Int) -> Endpoint {
- Endpoint(path: .chunked(count: count))
- }
- static func compression(_ compression: Compression) -> Endpoint {
- Endpoint(path: .compression(compression))
- }
- static var `default`: Endpoint { .get }
- static func delay(_ interval: Int) -> Endpoint {
- Endpoint(path: .delay(interval: interval))
- }
- static func digestAuth(forUser user: String = "user", password: String = "password") -> Endpoint {
- Endpoint(path: .digestAuth(username: user, password: password))
- }
- static func download(_ count: Int = 10_000, produceError: Bool = false) -> Endpoint {
- Endpoint(path: .download(count: count), queryItems: [.init(name: "shouldProduceError",
- value: "\(produceError)")])
- }
- static func hiddenBasicAuth(forUser user: String = "user", password: String = "password") -> Endpoint {
- Endpoint(path: .hiddenBasicAuth(username: user, password: password),
- headers: [.authorization(username: user, password: password)])
- }
- static func image(_ type: Image) -> Endpoint {
- Endpoint(path: .image(type))
- }
- static var ip: Endpoint {
- Endpoint(path: .ip)
- }
- static func method(_ method: HTTPMethod) -> Endpoint {
- Endpoint(path: .method(method), method: method)
- }
- static func payloads(_ count: Int) -> Endpoint {
- Endpoint(path: .payloads(count: count))
- }
- static func redirect(_ count: Int) -> Endpoint {
- Endpoint(path: .redirect(count: count))
- }
- static func redirectTo(_ url: String, code: Int? = nil) -> Endpoint {
- var items = [URLQueryItem(name: "url", value: url)]
- items = code.map { items + [.init(name: "statusCode", value: "\($0)")] } ?? items
- return Endpoint(path: .redirectTo, queryItems: items)
- }
- static func redirectTo(_ endpoint: Endpoint, code: Int? = nil) -> Endpoint {
- var items = [URLQueryItem(name: "url", value: endpoint.url.absoluteString)]
- items = code.map { items + [.init(name: "statusCode", value: "\($0)")] } ?? items
- return Endpoint(path: .redirectTo, queryItems: items)
- }
- static var responseHeaders: Endpoint {
- Endpoint(path: .responseHeaders)
- }
- static func status(_ code: Int) -> Endpoint {
- Endpoint(path: .status(code))
- }
- static func stream(_ count: Int) -> Endpoint {
- Endpoint(path: .stream(count: count))
- }
- static let upload: Endpoint = .init(path: .upload, method: .post, headers: [.contentType("application/octet-stream")])
- #if canImport(Darwin) && !canImport(FoundationNetworking)
- static var defaultCloseDelay: Int64 {
- if #available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) {
- 0
- } else if #available(macOS 11.3, iOS 14.5, tvOS 14.5, watchOS 7.4, *) {
- // iOS 14.5 to 14.7 have a bug where immediate connection closure will drop messages, so delay close by 60
- // milliseconds.
- 60
- } else {
- 0
- }
- }
- @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
- static func websocket(closeCode: URLSessionWebSocketTask.CloseCode = .normalClosure, closeDelay: Int64 = defaultCloseDelay) -> Endpoint {
- Endpoint(path: .websocket, queryItems: [.init(name: "closeCode", value: "\(closeCode.rawValue)"),
- .init(name: "closeDelay", value: "\(closeDelay)")])
- }
- @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
- static func websocketCount(_ count: Int = 2,
- closeCode: URLSessionWebSocketTask.CloseCode = .normalClosure,
- closeDelay: Int64 = defaultCloseDelay) -> Endpoint {
- Endpoint(path: .websocketCount(count), queryItems: [.init(name: "closeCode", value: "\(closeCode.rawValue)"),
- .init(name: "closeDelay", value: "\(closeDelay)")])
- }
- @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
- static let websocketEcho = Endpoint(path: .websocketEcho)
- static func websocketPings(count: Int = 5) -> Endpoint {
- Endpoint(path: .websocketPingCount(count))
- }
- #endif
- static var xml: Endpoint {
- Endpoint(path: .xml, headers: [.contentType("application/xml")])
- }
- var scheme = Scheme.http
- var port: Int { host.port(for: scheme) }
- var host = Host.localhost
- var path = Path.method(.get)
- var method: HTTPMethod = .get
- var headers: HTTPHeaders = .init()
- var timeout: TimeInterval = 60
- var queryItems: [URLQueryItem] = []
- var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
- func modifying<T>(_ keyPath: WritableKeyPath<Endpoint, T>, to value: T) -> Endpoint {
- var copy = self
- copy[keyPath: keyPath] = value
- return copy
- }
- }
- extension Endpoint: URLRequestConvertible {
- var urlRequest: URLRequest { try! asURLRequest() }
- func asURLRequest() throws -> URLRequest {
- var request = try URLRequest(url: asURL())
- request.method = method
- request.headers = headers
- request.timeoutInterval = timeout
- request.cachePolicy = cachePolicy
- return request
- }
- }
- extension Endpoint: URLConvertible {
- var url: URL { try! asURL() }
- func asURL() throws -> URL {
- var components = URLComponents()
- components.scheme = scheme.rawValue
- components.port = port
- components.host = host.rawValue
- components.path = path.string
- if !queryItems.isEmpty {
- components.queryItems = queryItems
- }
- return try components.asURL()
- }
- }
- final class EndpointSequence: URLRequestConvertible {
- enum Error: Swift.Error { case noRemainingEndpoints }
- private var remainingEndpoints: [Endpoint]
- init(endpoints: [Endpoint]) {
- remainingEndpoints = endpoints
- }
- func asURLRequest() throws -> URLRequest {
- guard !remainingEndpoints.isEmpty else { throw Error.noRemainingEndpoints }
- return try remainingEndpoints.removeFirst().asURLRequest()
- }
- }
- extension URLRequestConvertible where Self == EndpointSequence {
- static func endpoints(_ endpoints: Endpoint...) -> Self {
- EndpointSequence(endpoints: endpoints)
- }
- }
- extension Session {
- func request(_ endpoint: Endpoint,
- parameters: Parameters? = nil,
- encoding: any ParameterEncoding = URLEncoding.default,
- headers: HTTPHeaders? = nil,
- interceptor: (any RequestInterceptor)? = nil,
- requestModifier: RequestModifier? = nil) -> DataRequest {
- request(endpoint as (any URLConvertible),
- method: endpoint.method,
- parameters: parameters,
- encoding: encoding,
- headers: headers,
- interceptor: interceptor,
- requestModifier: requestModifier)
- }
- func request<Parameters: Encodable & Sendable>(_ endpoint: Endpoint,
- parameters: Parameters? = nil,
- encoder: any ParameterEncoder = URLEncodedFormParameterEncoder.default,
- headers: HTTPHeaders? = nil,
- interceptor: (any RequestInterceptor)? = nil,
- requestModifier: RequestModifier? = nil) -> DataRequest {
- request(endpoint as (any URLConvertible),
- method: endpoint.method,
- parameters: parameters,
- encoder: encoder,
- headers: headers,
- interceptor: interceptor,
- requestModifier: requestModifier)
- }
- func request(_ endpoint: Endpoint, interceptor: (any RequestInterceptor)? = nil) -> DataRequest {
- request(endpoint as (any URLRequestConvertible), interceptor: interceptor)
- }
- func streamRequest(_ endpoint: Endpoint,
- headers: HTTPHeaders? = nil,
- automaticallyCancelOnStreamError: Bool = false,
- interceptor: (any RequestInterceptor)? = nil,
- requestModifier: RequestModifier? = nil) -> DataStreamRequest {
- streamRequest(endpoint as (any URLConvertible),
- method: endpoint.method,
- headers: headers,
- automaticallyCancelOnStreamError: automaticallyCancelOnStreamError,
- interceptor: interceptor,
- requestModifier: requestModifier)
- }
- func streamRequest(_ endpoint: Endpoint,
- automaticallyCancelOnStreamError: Bool = false,
- interceptor: (any RequestInterceptor)? = nil) -> DataStreamRequest {
- streamRequest(endpoint as (any URLRequestConvertible),
- automaticallyCancelOnStreamError: automaticallyCancelOnStreamError,
- interceptor: interceptor)
- }
- func download<Parameters: Encodable & Sendable>(_ endpoint: Endpoint,
- parameters: Parameters? = nil,
- encoder: any ParameterEncoder = URLEncodedFormParameterEncoder.default,
- headers: HTTPHeaders? = nil,
- interceptor: (any RequestInterceptor)? = nil,
- requestModifier: RequestModifier? = nil,
- to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
- download(endpoint as (any URLConvertible),
- method: endpoint.method,
- parameters: parameters,
- encoder: encoder,
- headers: headers,
- interceptor: interceptor,
- requestModifier: requestModifier,
- to: destination)
- }
- func download(_ endpoint: Endpoint,
- parameters: Parameters? = nil,
- encoding: any ParameterEncoding = URLEncoding.default,
- headers: HTTPHeaders? = nil,
- interceptor: (any RequestInterceptor)? = nil,
- requestModifier: RequestModifier? = nil,
- to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
- download(endpoint as (any URLConvertible),
- method: endpoint.method,
- parameters: parameters,
- encoding: encoding,
- headers: headers,
- interceptor: interceptor,
- requestModifier: requestModifier,
- to: destination)
- }
- func download(_ endpoint: Endpoint,
- interceptor: (any RequestInterceptor)? = nil,
- to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
- download(endpoint as (any URLRequestConvertible), interceptor: interceptor, to: destination)
- }
- func upload(_ data: Data,
- to endpoint: Endpoint,
- headers: HTTPHeaders? = nil,
- interceptor: (any RequestInterceptor)? = nil,
- fileManager: FileManager = .default,
- requestModifier: RequestModifier? = nil) -> UploadRequest {
- upload(data, to: endpoint as (any URLConvertible),
- method: endpoint.method,
- headers: headers,
- interceptor: interceptor,
- fileManager: fileManager,
- requestModifier: requestModifier)
- }
- }
- extension Data {
- var asString: String {
- String(decoding: self, as: UTF8.self)
- }
- func asJSONObject() throws -> Any {
- try JSONSerialization.jsonObject(with: self, options: .allowFragments)
- }
- }
- struct TestResponse: Decodable {
- let headers: HTTPHeaders
- let origin: String
- let url: String
- let data: String?
- let form: [String: String]?
- let args: [String: String]
- }
- extension Alamofire.HTTPHeaders: Swift.Decodable {
- public init(from decoder: any Decoder) throws {
- let container = try decoder.singleValueContainer()
- let headers = try container.decode([HTTPHeader].self)
- self = .init(headers)
- }
- }
- extension Alamofire.HTTPHeader: Swift.Decodable {
- enum CodingKeys: String, CodingKey {
- case name, value
- }
- public init(from decoder: any Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- let name = try container.decode(String.self, forKey: .name)
- let value = try container.decode(String.self, forKey: .value)
- self = .init(name: name, value: value)
- }
- }
- struct TestParameters: Encodable {
- static let `default` = TestParameters(property: "property")
- let property: String
- }
- struct UploadResponse: Decodable {
- let bytes: Int
- }
|