TestHelpers.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. //
  2. // HTTPBin.swift
  3. //
  4. // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Alamofire
  25. import Foundation
  26. extension String {
  27. static let nonexistentDomain = "https://nonexistent-domain.org"
  28. }
  29. extension URL {
  30. static let nonexistentDomain = URL(string: .nonexistentDomain)!
  31. }
  32. struct Endpoint {
  33. enum Scheme: String {
  34. case http, https
  35. var port: Int {
  36. switch self {
  37. case .http: return 80
  38. case .https: return 443
  39. }
  40. }
  41. }
  42. enum Host: String {
  43. case localhost = "127.0.0.1"
  44. case httpBin = "httpbin.org"
  45. func port(for scheme: Scheme) -> Int {
  46. switch self {
  47. case .localhost: return 8080
  48. case .httpBin: return scheme.port
  49. }
  50. }
  51. }
  52. enum Path {
  53. case basicAuth(username: String, password: String)
  54. case bytes(count: Int)
  55. case chunked(count: Int)
  56. case compression(Compression)
  57. case delay(interval: Int)
  58. case digestAuth(qop: String = "auth", username: String, password: String)
  59. case download(count: Int)
  60. case hiddenBasicAuth(username: String, password: String)
  61. case image(Image)
  62. case ip
  63. case method(HTTPMethod)
  64. case payloads(count: Int)
  65. case redirect(count: Int)
  66. case redirectTo
  67. case responseHeaders
  68. case status(Int)
  69. case stream(count: Int)
  70. case xml
  71. var string: String {
  72. switch self {
  73. case let .basicAuth(username: username, password: password):
  74. return "/basic-auth/\(username)/\(password)"
  75. case let .bytes(count):
  76. return "/bytes/\(count)"
  77. case let .chunked(count):
  78. return "/chunked/\(count)"
  79. case let .compression(compression):
  80. return "/\(compression.rawValue)"
  81. case let .delay(interval):
  82. return "/delay/\(interval)"
  83. case let .digestAuth(qop, username, password):
  84. return "/digest-auth/\(qop)/\(username)/\(password)"
  85. case let .download(count):
  86. return "/download/\(count)"
  87. case let .hiddenBasicAuth(username, password):
  88. return "/hidden-basic-auth/\(username)/\(password)"
  89. case let .image(type):
  90. return "/image/\(type.rawValue)"
  91. case .ip:
  92. return "/ip"
  93. case let .method(method):
  94. return "/\(method.rawValue.lowercased())"
  95. case let .payloads(count):
  96. return "/payloads/\(count)"
  97. case let .redirect(count):
  98. return "/redirect/\(count)"
  99. case .redirectTo:
  100. return "/redirect-to"
  101. case .responseHeaders:
  102. return "/response-headers"
  103. case let .status(code):
  104. return "/status/\(code)"
  105. case let .stream(count):
  106. return "/stream/\(count)"
  107. case .xml:
  108. return "/xml"
  109. }
  110. }
  111. }
  112. enum Image: String {
  113. case jpeg
  114. }
  115. enum Compression: String {
  116. case brotli, gzip, deflate
  117. }
  118. static var get: Endpoint { method(.get) }
  119. static func basicAuth(forUser user: String = "user", password: String = "password") -> Endpoint {
  120. Endpoint(path: .basicAuth(username: user, password: password))
  121. }
  122. static func bytes(_ count: Int) -> Endpoint {
  123. Endpoint(path: .bytes(count: count))
  124. }
  125. static func chunked(_ count: Int) -> Endpoint {
  126. Endpoint(path: .chunked(count: count))
  127. }
  128. static func compression(_ compression: Compression) -> Endpoint {
  129. Endpoint(path: .compression(compression))
  130. }
  131. static var `default`: Endpoint { .get }
  132. static func delay(_ interval: Int) -> Endpoint {
  133. Endpoint(path: .delay(interval: interval))
  134. }
  135. static func digestAuth(forUser user: String = "user", password: String = "password") -> Endpoint {
  136. Endpoint(path: .digestAuth(username: user, password: password))
  137. }
  138. static func download(_ count: Int = 10_000, produceError: Bool = false) -> Endpoint {
  139. Endpoint(path: .download(count: count), queryItems: [.init(name: "shouldProduceError",
  140. value: "\(produceError)")])
  141. }
  142. static func hiddenBasicAuth(forUser user: String = "user", password: String = "password") -> Endpoint {
  143. Endpoint(path: .hiddenBasicAuth(username: user, password: password),
  144. headers: [.authorization(username: user, password: password)])
  145. }
  146. static func image(_ type: Image) -> Endpoint {
  147. Endpoint(path: .image(type))
  148. }
  149. static var ip: Endpoint {
  150. Endpoint(path: .ip)
  151. }
  152. static func method(_ method: HTTPMethod) -> Endpoint {
  153. Endpoint(path: .method(method), method: method)
  154. }
  155. static func payloads(_ count: Int) -> Endpoint {
  156. Endpoint(path: .payloads(count: count))
  157. }
  158. static func redirect(_ count: Int) -> Endpoint {
  159. Endpoint(path: .redirect(count: count))
  160. }
  161. static func redirectTo(_ url: String, code: Int? = nil) -> Endpoint {
  162. var items = [URLQueryItem(name: "url", value: url)]
  163. items = code.map { items + [.init(name: "statusCode", value: "\($0)")] } ?? items
  164. return Endpoint(path: .redirectTo, queryItems: items)
  165. }
  166. static func redirectTo(_ endpoint: Endpoint, code: Int? = nil) -> Endpoint {
  167. var items = [URLQueryItem(name: "url", value: endpoint.url.absoluteString)]
  168. items = code.map { items + [.init(name: "statusCode", value: "\($0)")] } ?? items
  169. return Endpoint(path: .redirectTo, queryItems: items)
  170. }
  171. static var responseHeaders: Endpoint {
  172. Endpoint(path: .responseHeaders)
  173. }
  174. static func status(_ code: Int) -> Endpoint {
  175. Endpoint(path: .status(code))
  176. }
  177. static func stream(_ count: Int) -> Endpoint {
  178. Endpoint(path: .stream(count: count))
  179. }
  180. static var xml: Endpoint {
  181. Endpoint(path: .xml, headers: [.contentType("application/xml")])
  182. }
  183. var scheme = Scheme.http
  184. var port: Int { host.port(for: scheme) }
  185. var host = Host.localhost
  186. var path = Path.method(.get)
  187. var method: HTTPMethod = .get
  188. var headers: HTTPHeaders = .init()
  189. var timeout: TimeInterval = 60
  190. var queryItems: [URLQueryItem] = []
  191. var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
  192. func modifying<T>(_ keyPath: WritableKeyPath<Endpoint, T>, to value: T) -> Endpoint {
  193. var copy = self
  194. copy[keyPath: keyPath] = value
  195. return copy
  196. }
  197. }
  198. extension Endpoint: URLRequestConvertible {
  199. var urlRequest: URLRequest { try! asURLRequest() }
  200. func asURLRequest() throws -> URLRequest {
  201. var request = URLRequest(url: try asURL())
  202. request.method = method
  203. request.headers = headers
  204. request.timeoutInterval = timeout
  205. request.cachePolicy = cachePolicy
  206. return request
  207. }
  208. }
  209. extension Endpoint: URLConvertible {
  210. var url: URL { try! asURL() }
  211. func asURL() throws -> URL {
  212. var components = URLComponents()
  213. components.scheme = scheme.rawValue
  214. components.port = port
  215. components.host = host.rawValue
  216. components.path = path.string
  217. if !queryItems.isEmpty {
  218. components.queryItems = queryItems
  219. }
  220. return try components.asURL()
  221. }
  222. }
  223. extension Session {
  224. func request(_ endpoint: Endpoint,
  225. parameters: Parameters? = nil,
  226. encoding: ParameterEncoding = URLEncoding.default,
  227. headers: HTTPHeaders? = nil,
  228. interceptor: RequestInterceptor? = nil,
  229. requestModifier: RequestModifier? = nil) -> DataRequest {
  230. request(endpoint as URLConvertible,
  231. method: endpoint.method,
  232. parameters: parameters,
  233. encoding: encoding,
  234. headers: headers,
  235. interceptor: interceptor,
  236. requestModifier: requestModifier)
  237. }
  238. func request<Parameters: Encodable>(_ endpoint: Endpoint,
  239. parameters: Parameters? = nil,
  240. encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
  241. headers: HTTPHeaders? = nil,
  242. interceptor: RequestInterceptor? = nil,
  243. requestModifier: RequestModifier? = nil) -> DataRequest {
  244. request(endpoint as URLConvertible,
  245. method: endpoint.method,
  246. parameters: parameters,
  247. encoder: encoder,
  248. headers: headers,
  249. interceptor: interceptor,
  250. requestModifier: requestModifier)
  251. }
  252. func request(_ endpoint: Endpoint, interceptor: RequestInterceptor? = nil) -> DataRequest {
  253. request(endpoint as URLRequestConvertible, interceptor: interceptor)
  254. }
  255. func streamRequest(_ endpoint: Endpoint,
  256. headers: HTTPHeaders? = nil,
  257. automaticallyCancelOnStreamError: Bool = false,
  258. interceptor: RequestInterceptor? = nil,
  259. requestModifier: RequestModifier? = nil) -> DataStreamRequest {
  260. streamRequest(endpoint as URLConvertible,
  261. method: endpoint.method,
  262. headers: headers,
  263. automaticallyCancelOnStreamError: automaticallyCancelOnStreamError,
  264. interceptor: interceptor,
  265. requestModifier: requestModifier)
  266. }
  267. func streamRequest(_ endpoint: Endpoint,
  268. automaticallyCancelOnStreamError: Bool = false,
  269. interceptor: RequestInterceptor? = nil) -> DataStreamRequest {
  270. streamRequest(endpoint as URLRequestConvertible,
  271. automaticallyCancelOnStreamError: automaticallyCancelOnStreamError,
  272. interceptor: interceptor)
  273. }
  274. func download<Parameters: Encodable>(_ endpoint: Endpoint,
  275. parameters: Parameters? = nil,
  276. encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
  277. headers: HTTPHeaders? = nil,
  278. interceptor: RequestInterceptor? = nil,
  279. requestModifier: RequestModifier? = nil,
  280. to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
  281. download(endpoint as URLConvertible,
  282. method: endpoint.method,
  283. parameters: parameters,
  284. encoder: encoder,
  285. headers: headers,
  286. interceptor: interceptor,
  287. requestModifier: requestModifier,
  288. to: destination)
  289. }
  290. func download(_ endpoint: Endpoint,
  291. parameters: Parameters? = nil,
  292. encoding: ParameterEncoding = URLEncoding.default,
  293. headers: HTTPHeaders? = nil,
  294. interceptor: RequestInterceptor? = nil,
  295. requestModifier: RequestModifier? = nil,
  296. to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
  297. download(endpoint as URLConvertible,
  298. method: endpoint.method,
  299. parameters: parameters,
  300. encoding: encoding,
  301. headers: headers,
  302. interceptor: interceptor,
  303. requestModifier: requestModifier,
  304. to: destination)
  305. }
  306. func download(_ endpoint: Endpoint,
  307. interceptor: RequestInterceptor? = nil,
  308. to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
  309. download(endpoint as URLRequestConvertible, interceptor: interceptor, to: destination)
  310. }
  311. func upload(_ data: Data,
  312. to endpoint: Endpoint,
  313. headers: HTTPHeaders? = nil,
  314. interceptor: RequestInterceptor? = nil,
  315. fileManager: FileManager = .default,
  316. requestModifier: RequestModifier? = nil) -> UploadRequest {
  317. upload(data, to: endpoint as URLConvertible,
  318. method: endpoint.method,
  319. headers: headers,
  320. interceptor: interceptor,
  321. fileManager: fileManager,
  322. requestModifier: requestModifier)
  323. }
  324. }
  325. extension Data {
  326. var asString: String {
  327. String(decoding: self, as: UTF8.self)
  328. }
  329. }
  330. struct TestResponse: Decodable {
  331. let headers: [String: String]
  332. let origin: String
  333. let url: String?
  334. let data: String?
  335. let form: [String: String]?
  336. let args: [String: String]?
  337. }
  338. struct TestParameters: Encodable {
  339. static let `default` = TestParameters(property: "property")
  340. let property: String
  341. }