├── .github └── main.workflow ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── SwiftNIOMock │ ├── Router.swift │ ├── Server.swift │ └── Service.swift ├── SwiftNIOMock.podspec ├── SwiftNIOMock.xcodeproj ├── CNIOAtomics_Info.plist ├── CNIODarwin_Info.plist ├── CNIOHTTPParser_Info.plist ├── CNIOLinux_Info.plist ├── CNIOSHA1_Info.plist ├── CNIOZlib_Info.plist ├── NIOConcurrencyHelpers_Info.plist ├── NIOHTTP1_Info.plist ├── NIOPriorityQueue_Info.plist ├── NIO_Info.plist ├── SwiftNIOMockTests_Info.plist ├── SwiftNIOMock_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── SwiftNIOMock-Package.xcscheme ├── SwiftNIOMock.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftNIOMockExample ├── SwiftNIOMockExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── SwiftNIOMockExample.xcscheme ├── SwiftNIOMockExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift └── SwiftNIOMockExampleUITests │ ├── Info.plist │ ├── Recordings │ ├── testDelay.json │ ├── testGET.json │ ├── testPOST.json │ └── testRoute.json │ └── SwiftNIOMockExampleUITests.swift └── Tests ├── LinuxMain.swift └── SwiftNIOMockTests ├── SwiftNIOMockTests.swift └── XCTestManifests.swift /.github/main.workflow: -------------------------------------------------------------------------------- 1 | workflow "Test" { 2 | on = "push" 3 | resolves = ["test"] 4 | } 5 | 6 | action "test" { 7 | uses = "ilyapuchka/SwiftGitHubAction@master" 8 | args = "test" 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Xcode 4 | # 5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | .build/ 44 | .swiftpm/xcode/ 45 | 46 | # CocoaPods 47 | # 48 | # We recommend against adding the Pods directory to your .gitignore. However 49 | # you should judge for yourself, the pros and cons are mentioned at: 50 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 51 | # 52 | Pods/ 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # fastlane 62 | # 63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 64 | # screenshots whenever they are needed. 65 | # For more information about the recommended setup visit: 66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 67 | 68 | fastlane/report.xml 69 | fastlane/Preview.html 70 | fastlane/screenshots/**/*.png 71 | fastlane/test_output 72 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "cocoapods", '=1.6.0.beta.2' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.11) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | atomos (0.1.3) 11 | claide (1.0.2) 12 | cocoapods (1.6.0.beta.2) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.2, < 2.0) 15 | cocoapods-core (= 1.6.0.beta.2) 16 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 17 | cocoapods-downloader (>= 1.2.2, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.3.1, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (~> 2.0.1) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.6.6) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.3, >= 1.3.1) 30 | xcodeproj (>= 1.7.0, < 2.0) 31 | cocoapods-core (1.6.0.beta.2) 32 | activesupport (>= 4.0.2, < 6) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.2) 36 | cocoapods-downloader (1.2.2) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.0) 40 | cocoapods-stats (1.0.0) 41 | cocoapods-trunk (1.3.1) 42 | nap (>= 0.8, < 2.0) 43 | netrc (~> 0.11) 44 | cocoapods-try (1.1.0) 45 | colored2 (3.1.2) 46 | concurrent-ruby (1.1.4) 47 | escape (0.0.4) 48 | fourflusher (2.0.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | minitest (5.11.3) 54 | molinillo (0.6.6) 55 | nanaimo (0.2.6) 56 | nap (1.1.0) 57 | netrc (0.11.0) 58 | ruby-macho (1.3.1) 59 | thread_safe (0.3.6) 60 | tzinfo (1.2.5) 61 | thread_safe (~> 0.1) 62 | xcodeproj (1.7.0) 63 | CFPropertyList (>= 2.3.3, < 4.0) 64 | atomos (~> 0.1.3) 65 | claide (>= 1.0.2, < 2.0) 66 | colored2 (~> 3.1) 67 | nanaimo (~> 0.2.6) 68 | 69 | PLATFORMS 70 | ruby 71 | 72 | DEPENDENCIES 73 | cocoapods (= 1.6.0.beta.2) 74 | 75 | BUNDLED WITH 76 | 1.16.6 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ilya Puchka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CommonParsers", 6 | "repositoryURL": "https://github.com/ilyapuchka/common-parsers.git", 7 | "state": { 8 | "branch": "master", 9 | "revision": "2386ef5d444746ad77a70296d878683ae0f9c9a2", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "swift-nio", 15 | "repositoryURL": "https://github.com/apple/swift-nio.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd", 19 | "version": "1.14.1" 20 | } 21 | }, 22 | { 23 | "package": "swift-nio-zlib-support", 24 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", 28 | "version": "1.0.0" 29 | } 30 | }, 31 | { 32 | "package": "Prelude", 33 | "repositoryURL": "https://github.com/pointfreeco/swift-prelude.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "9240a1f951c382e1c7387762c6cd94042baba531", 37 | "version": null 38 | } 39 | }, 40 | { 41 | "package": "URLFormat", 42 | "repositoryURL": "https://github.com/ilyapuchka/URLFormat.git", 43 | "state": { 44 | "branch": "master", 45 | "revision": "d2aa0da9b356fdd52a756f8ea047d181ee3f1553", 46 | "version": null 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // 3 | // Package.swift 4 | // SwiftNIOMock 5 | // 6 | // Created by Ilya Puchka on 18/12/2018. 7 | // 8 | import PackageDescription 9 | 10 | let package = Package( 11 | name: "SwiftNIOMock", 12 | products: [ 13 | .library(name: "SwiftNIOMock", targets: ["SwiftNIOMock"]), 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/apple/swift-nio.git", from: "1.12.0"), 17 | .package(url: "https://github.com/ilyapuchka/URLFormat.git", .branch("master")), 18 | ], 19 | targets: [ 20 | .target(name: "SwiftNIOMock", dependencies: ["NIO", "NIOHTTP1", "URLFormat"]), 21 | .testTarget(name: "SwiftNIOMockTests", dependencies: ["SwiftNIOMock"]), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftNIOMock 2 | A web server based on [SwiftNIO](https://github.com/apple/swift-nio) designed to be used as a mock server in UI automation tests. 3 | 4 | When running UI tests against real server several issues can come along: network can be unstable, content on the remote server can change, some of the test scenarios may require actions in some external system, and in general making network calls slows down all the tests. 5 | 6 | SwiftNIOMock aims to address these issues by providing a mock web server implementation that runs on the `localhost` and which app should access instead of a real server when running under UI test. Unlike other solutions like registering custom `URLProtocol` using mock server requires only switching the app to the `localhost` and gives you much more flexibility and control as the server is controlled by the test and you can change its state from within the test scenarios. 7 | 8 | SwiftNIOMock supports three common scenarios: 9 | 10 | - redirect requests to the real service using `redirect` middleware. This can be used for logging purposes which can make debugging easier as all the network logs will be in the console log of the test runner process 11 | 12 | - mock endpoints using `router` middleware. This is useful when you need to have more control over the state of the mock server and allows you to completely mock out all network requests the app makes (although it can be used along with `redirect` middleware). This is usually needed when tests scenarios require actions in some external systems. When you control the state of the server completely you can easily fake such actions 13 | 14 | - record & replay network calls using `redirect` middleware provided with the implementation of `URLSession` that can record and replay requests (using [Vinyl](https://github.com/Velhotes/Vinyl) or any other similar implementation). This is useful to ensure that tests receive the same data between runs and aims to guard against changes on the remote server that are out of your control 15 | 16 | ## Usage 17 | 18 | In the test create an instance of the server and start it in the `setUp` method and provide it with a middleware to handle requests. In the `tearDown` method stop the server. 19 | 20 | ```swift 21 | override func setUp() { 22 | server = Server(port: 8080, handler: <#Middleware#>) 23 | try! server.start() 24 | } 25 | 26 | override func tearDown() { 27 | try! server.stop() 28 | server = nil 29 | } 30 | ``` 31 | 32 | A middleware is a function that is based on an incoming request can modify a response. 33 | 34 | ```swift 35 | typealias Middleware = ( 36 | _ request: Server.HTTPHandler.Request, 37 | _ response: Server.HTTPHandler.Response, 38 | _ next: @escaping () -> Void 39 | ) -> Void 40 | ``` 41 | 42 | When middleware function is done with response it should call the `next` closure (it's fine to call it asynchronously) that is passed to it to return control to the server. You can write your own middleware or use those provided by SwiftNIOMock, which are `redirect`, `router` and `delay`. Here is an example of a simple middleware that echoes back any incoming request: 43 | 44 | ```swift 45 | func echo( 46 | request: Server.HTTPHandler.Request, 47 | response: Server.HTTPHandler.Response, 48 | next: @escaping () -> Void 49 | ) { 50 | response.statusCode = .ok 51 | response.body = request.body 52 | next() 53 | } 54 | ``` 55 | 56 | `redirect` middleware allows you to redirect incoming requests and intercept responses. It also accepts an instance of `URLSession` (`URLSession.shared` session by default) that it will use to perform a request, which allows it to record & replay all requests. Check out `SwiftNIOMockExampleUITests.swift` to see the example of how to use SwiftNIOMock in record & replay mode powered by Vinyl. 57 | 58 | ```swift 59 | func redirect( 60 | session: URLSession = URLSession.shared, 61 | request override: @escaping (Server.HTTPHandler.Request) -> Server.HTTPHandler.Request, 62 | response intercept: @escaping (Server.HTTPHandler.Response) -> Void = { _ in } 63 | ) -> Middleware 64 | ``` 65 | 66 | `router` middleware allows you to return arbitrary middleware based on a request, i.e. its method and path. If request can't be handled a fallback middleware passed as `notFound` parameter will be used. 67 | 68 | ```swift 69 | func router( 70 | route: @escaping (Server.HTTPHandler.Request) -> Middleware?, 71 | notFound: @escaping Middleware 72 | ) -> Middleware 73 | ``` 74 | 75 | To see an example of usage SwiftNIOMock in UI tests check SwiftNIOMockExample. 76 | 77 | ### Routing 78 | 79 | While you can use the `router` function to create a routing middleware for the mock server on the practice it is a good idea to break it down into smaller services. For that you can use `Service` type: 80 | 81 | ```swift 82 | let fooService = Service { 83 | route({ $0.head.uri == "/foo" }) { (request, response, next) in 84 | response.sendString(.ok, value: "Foo") 85 | next() 86 | } 87 | } 88 | let barService = Service { 89 | route({ $0.head.uri == "/bar" }) { (request, response, next) in 90 | response.sendString(.ok, value: "Bar") 91 | next() 92 | } 93 | } 94 | 95 | let router = SwiftNIOMock.router(services: [ 96 | fooService, 97 | barService 98 | ]) 99 | ``` 100 | 101 | You can as well implement your own service by subclassing `Service`: 102 | 103 | ```swift 104 | class HelloService: Service { 105 | override init() { 106 | super.init() 107 | routes { 108 | route({ $0.head.uri == "/helloworld" }) { (request, response, next) in 109 | response.sendString(.ok, value: "Hello World!") 110 | next() 111 | } 112 | } 113 | } 114 | } 115 | let helloService = HelloService() 116 | let router = SwiftNIOMock.router(services: [helloService]) 117 | ``` 118 | 119 | You can register routes using closure predicates as in above examples or using [URLFormat](https://github.com/ilyapuchka/URLFormat): 120 | 121 | ```swift 122 | import URLFormat 123 | 124 | routes { 125 | route(GET/.helloworld) { (request, response, next) in 126 | response.sendString(.ok, value: "Hello World!") 127 | next() 128 | } 129 | } 130 | ``` 131 | 132 | You can also use a shorthand syntax to bind routes to a service KeyPath or a function with `Encodable` return value type: 133 | 134 | ```swift 135 | class HelloService: Service { 136 | let users: [String] 137 | 138 | func user(byName name: String) -> String? { 139 | users.first { $0 == name } 140 | } 141 | 142 | init(users: [String]) { 143 | self.users = users 144 | super.init() 145 | 146 | routes { 147 | GET/.users == \HelloService.users 148 | GET/.users/.string == HelloService.user(byName:) 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | As routes are bound to key paths or functions you can mutate the state of your service and these changes will be reflected in the later responses. 155 | 156 | When binding a route to a function its parameters types must match `URLFormat` type, i.e. `URLFormat<((String, String), Int)>` can be only bound to a function `(String, String, Int) -> T where T: Encodable`. Additionally you can add a `Server.HTTPHandler.Request` parameter as the last parameter of the function to access a request body data. 157 | 158 | ## Installation 159 | 160 | ```swift 161 | import PackageDescription 162 | 163 | let package = Package( 164 | dependencies: [ 165 | .package(url: "https://github.com/ilyapuchka/SwiftNIOMock.git", .branch("master")), 166 | ] 167 | ) 168 | ``` 169 | 170 | -------------------------------------------------------------------------------- /Sources/SwiftNIOMock/Router.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NIO 3 | import NIOHTTP1 4 | import URLFormat 5 | import CommonParsers 6 | import Prelude 7 | 8 | public typealias RouteMiddleware = ( 9 | _ request: Server.HTTPHandler.Request, 10 | _ parameters: A, 11 | _ response: Server.HTTPHandler.Response, 12 | _ next: @escaping () -> Void 13 | ) -> Void 14 | 15 | public func route(_ predicate: @escaping (Server.HTTPHandler.Request) -> Bool, response: @escaping Middleware) -> Middleware { 16 | return { request, httpResponse, next in 17 | predicate(request) 18 | ? response(request, httpResponse, next) 19 | : next() 20 | } 21 | } 22 | 23 | public func route(_ method: HTTPMethod, _ predicate: @escaping (Server.HTTPHandler.Request) -> Bool, response: @escaping Middleware) -> Middleware { 24 | return { request, httpResponse, next in 25 | request.head.method == method && predicate(request) 26 | ? response(request, httpResponse, next) 27 | : next() 28 | } 29 | } 30 | 31 | public func route(_ format: URLFormat, response: @escaping Middleware) -> Middleware { 32 | route(format, response: { response($0, $2, $3) }) 33 | } 34 | 35 | public func route(_ format: URLFormat, response: @escaping RouteMiddleware) -> Middleware { 36 | return { request, httpResponse, next in 37 | do { 38 | let components = URLRequestComponents( 39 | method: String(describing: request.head.method), 40 | urlComponents: URLComponents(string: request.head.uri)! 41 | ) 42 | guard let params = try format.parse(components) else { 43 | return next() 44 | } 45 | response(request, params, httpResponse, next) 46 | } catch { 47 | next() 48 | } 49 | } 50 | } 51 | 52 | extension ServiceProtocol where Self: Service { 53 | public func bind(format: URLFormat, to keyPath: KeyPath) -> Middleware { 54 | return route(format, response: { [weak self] _, params, response, next in 55 | guard let self = self else { return next() } 56 | try! response.sendJSON(.ok, value: self[keyPath: keyPath]) 57 | next() 58 | }) 59 | } 60 | public func bind(format: URLFormat, to value: @escaping (A) throws -> T) -> Middleware { 61 | return route(format, response: { [weak self] _, params, response, next in 62 | guard let _ = self else { return next() } 63 | try! response.sendJSON(.ok, value: value(params)) 64 | next() 65 | }) 66 | } 67 | public func bind(format: URLFormat, to value: @escaping (A, Server.HTTPHandler.Request) throws -> T) -> Middleware { 68 | return route(format, response: { [weak self] request, params, response, next in 69 | guard let _ = self else { return next() } 70 | try! response.sendJSON(.ok, value: value(params, request)) 71 | next() 72 | }) 73 | } 74 | } 75 | 76 | public func ==(lhs: URLFormat, rhs: KeyPath) -> (S) -> Middleware { 77 | return { service in 78 | service.bind(format: lhs, to: rhs) 79 | } 80 | } 81 | 82 | public func ==(lhs: URLFormat, rhs: @escaping (S) -> (A) throws -> T) -> (S) -> Middleware { 83 | return { (service: S) in 84 | service.bind(format: lhs, to: { [unowned service] in 85 | try rhs(service)($0) 86 | }) 87 | } 88 | } 89 | 90 | public func ==(lhs: URLFormat<(A, B)>, rhs: @escaping (S) -> (A, B) throws -> T) -> (S) -> Middleware { 91 | return { (service: S) in 92 | service.bind(format: lhs, to: { [unowned service] in 93 | try rhs(service)($0.0, $0.1) 94 | }) 95 | } 96 | } 97 | 98 | public func ==(lhs: URLFormat<((A, B), C)>, rhs: @escaping (S) -> (A, B, C) throws -> T) -> (S) -> Middleware { 99 | return { (service: S) in 100 | service.bind(format: lhs, to: { [unowned service] in 101 | try rhs(service)($0.0.0, $0.0.1, $0.1) 102 | }) 103 | } 104 | } 105 | 106 | public func ==(lhs: URLFormat<(((A, B), C), D)>, rhs: @escaping (S) -> (A, B, C, D) throws -> T) -> (S) -> Middleware { 107 | return { (service: S) in 108 | service.bind(format: lhs, to: { [unowned service] in 109 | try rhs(service)($0.0.0.0, $0.0.0.1, $0.0.1, $0.1) 110 | }) 111 | } 112 | } 113 | 114 | public func ==(lhs: URLFormat<((((A, B), C), D), E)>, rhs: @escaping (S) -> (A, B, C, D, E) throws -> T) -> (S) -> Middleware { 115 | return { (service: S) in 116 | service.bind(format: lhs, to: { [unowned service] in 117 | try rhs(service)($0.0.0.0.0, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1) 118 | }) 119 | } 120 | } 121 | 122 | public func ==(lhs: URLFormat<(((((A, B), C), D), E), F)>, rhs: @escaping (S) -> (A, B, C, D, E, F) throws -> T) -> (S) -> Middleware { 123 | return { (service: S) in 124 | service.bind(format: lhs, to: { [unowned service] in 125 | try rhs(service)($0.0.0.0.0.0, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1) 126 | }) 127 | } 128 | } 129 | 130 | public func ==(lhs: URLFormat<((((((A, B), C), D), E), F), G)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G) throws -> T) -> (S) -> Middleware { 131 | return { (service: S) in 132 | service.bind(format: lhs, to: { [unowned service] in 133 | try rhs(service)($0.0.0.0.0.0.0, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1) 134 | }) 135 | } 136 | } 137 | 138 | public func ==(lhs: URLFormat<(((((((A, B), C), D), E), F), G), H)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G, H) throws -> T) -> (S) -> Middleware { 139 | return { (service: S) in 140 | service.bind(format: lhs, to: { [unowned service] in 141 | try rhs(service)($0.0.0.0.0.0.0.0, $0.0.0.0.0.0.0.1, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1) 142 | }) 143 | } 144 | } 145 | 146 | public func ==(lhs: URLFormat<((((((((A, B), C), D), E), F), G), H), I)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G, H, I) throws -> T) -> (S) -> Middleware { 147 | return { (service: S) in 148 | service.bind(format: lhs, to: { [unowned service] in 149 | try rhs(service)($0.0.0.0.0.0.0.0.0, $0.0.0.0.0.0.0.0.1, $0.0.0.0.0.0.0.1, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1) 150 | }) 151 | } 152 | } 153 | 154 | public func ==(lhs: URLFormat<(((((((((A, B), C), D), E), F), G), H), I), J)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G, H, I, J) throws -> T) -> (S) -> Middleware { 155 | return { (service: S) in 156 | service.bind(format: lhs, to: { [unowned service] in 157 | try rhs(service)($0.0.0.0.0.0.0.0.0.0, $0.0.0.0.0.0.0.0.0.1, $0.0.0.0.0.0.0.0.1, $0.0.0.0.0.0.0.1, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1) 158 | }) 159 | } 160 | } 161 | 162 | public func ==(lhs: URLFormat, rhs: @escaping (S) -> (A, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 163 | return { (service: S) in 164 | service.bind(format: lhs, to: { [unowned service] in 165 | try rhs(service)($0, $1) 166 | }) 167 | } 168 | } 169 | 170 | public func ==(lhs: URLFormat<(A, B)>, rhs: @escaping (S) -> (A, B, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 171 | return { (service: S) in 172 | service.bind(format: lhs, to: { [unowned service] in 173 | try rhs(service)($0.0, $0.1, $1) 174 | }) 175 | } 176 | } 177 | 178 | public func ==(lhs: URLFormat<((A, B), C)>, rhs: @escaping (S) -> (A, B, C, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 179 | return { (service: S) in 180 | service.bind(format: lhs, to: { [unowned service] in 181 | try rhs(service)($0.0.0, $0.0.1, $0.1, $1) 182 | }) 183 | } 184 | } 185 | 186 | public func ==(lhs: URLFormat<(((A, B), C), D)>, rhs: @escaping (S) -> (A, B, C, D, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 187 | return { (service: S) in 188 | service.bind(format: lhs, to: { [unowned service] in 189 | try rhs(service)($0.0.0.0, $0.0.0.1, $0.0.1, $0.1, $1) 190 | }) 191 | } 192 | } 193 | 194 | public func ==(lhs: URLFormat<((((A, B), C), D), E)>, rhs: @escaping (S) -> (A, B, C, D, E, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 195 | return { (service: S) in 196 | service.bind(format: lhs, to: { [unowned service] in 197 | try rhs(service)($0.0.0.0.0, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1, $1) 198 | }) 199 | } 200 | } 201 | 202 | public func ==(lhs: URLFormat<(((((A, B), C), D), E), F)>, rhs: @escaping (S) -> (A, B, C, D, E, F, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 203 | return { (service: S) in 204 | service.bind(format: lhs, to: { [unowned service] in 205 | try rhs(service)($0.0.0.0.0.0, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1, $1) 206 | }) 207 | } 208 | } 209 | 210 | public func ==(lhs: URLFormat<((((((A, B), C), D), E), F), G)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 211 | return { (service: S) in 212 | service.bind(format: lhs, to: { [unowned service] in 213 | try rhs(service)($0.0.0.0.0.0.0, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1, $1) 214 | }) 215 | } 216 | } 217 | 218 | public func ==(lhs: URLFormat<(((((((A, B), C), D), E), F), G), H)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G, H, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 219 | return { (service: S) in 220 | service.bind(format: lhs, to: { [unowned service] in 221 | try rhs(service)($0.0.0.0.0.0.0.0, $0.0.0.0.0.0.0.1, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1, $1) 222 | }) 223 | } 224 | } 225 | 226 | public func ==(lhs: URLFormat<((((((((A, B), C), D), E), F), G), H), I)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G, H, I, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 227 | return { (service: S) in 228 | service.bind(format: lhs, to: { [unowned service] in 229 | try rhs(service)($0.0.0.0.0.0.0.0.0, $0.0.0.0.0.0.0.0.1, $0.0.0.0.0.0.0.1, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1, $1) 230 | }) 231 | } 232 | } 233 | 234 | public func ==(lhs: URLFormat<(((((((((A, B), C), D), E), F), G), H), I), J)>, rhs: @escaping (S) -> (A, B, C, D, E, F, G, H, I, J, Server.HTTPHandler.Request) throws -> T) -> (S) -> Middleware { 235 | return { (service: S) in 236 | service.bind(format: lhs, to: { [unowned service] in 237 | try rhs(service)($0.0.0.0.0.0.0.0.0.0, $0.0.0.0.0.0.0.0.0.1, $0.0.0.0.0.0.0.0.1, $0.0.0.0.0.0.0.1, $0.0.0.0.0.0.1, $0.0.0.0.0.1, $0.0.0.0.1, $0.0.0.1, $0.0.1, $0.1, $1) 238 | }) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Sources/SwiftNIOMock/Server.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NIO 3 | import NIOHTTP1 4 | 5 | open class Server { 6 | public let port: Int 7 | 8 | let handler: Middleware 9 | private(set) var group: EventLoopGroup! 10 | private(set) var bootstrap: ServerBootstrap! 11 | private(set) var serverChannel: Channel! 12 | 13 | public init(port: Int, handler: @escaping Middleware = { _, _, next in next() }) { 14 | self.port = port 15 | self.handler = handler 16 | } 17 | 18 | func bootstrapServer(handler: @escaping Middleware) -> ServerBootstrap { 19 | return ServerBootstrap(group: group) 20 | .serverChannelOption(ChannelOptions.backlog, value: 256) 21 | .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) 22 | .childChannelInitializer { channel in 23 | channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true) 24 | .then { 25 | channel.pipeline.add(handler: HTTPHandler(handler: handler)) 26 | }.then { 27 | channel.pipeline.add(handler: HTTPResponseCompressor()) 28 | } 29 | } 30 | .childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1) 31 | .childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) 32 | .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 1) 33 | } 34 | 35 | public func start() throws { 36 | group = group ?? MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 37 | bootstrap = bootstrap ?? bootstrapServer(handler: handler) 38 | 39 | serverChannel = try bootstrap.bind(host: "localhost", port: port).wait() 40 | print("Server listening on:", serverChannel.localAddress!) 41 | 42 | serverChannel.closeFuture.whenComplete { 43 | print("Server stopped") 44 | } 45 | } 46 | 47 | public func stop() throws { 48 | try serverChannel?.close().wait() 49 | try group.syncShutdownGracefully() 50 | serverChannel = nil 51 | bootstrap = nil 52 | group = nil 53 | } 54 | 55 | deinit { 56 | print("Server released") 57 | } 58 | 59 | } 60 | 61 | extension Server { 62 | public class HTTPHandler: ChannelInboundHandler { 63 | public typealias InboundIn = HTTPServerRequestPart 64 | 65 | private var state: State = .idle 66 | let handler: Middleware 67 | 68 | init(handler: @escaping Middleware) { 69 | self.handler = handler 70 | } 71 | 72 | public func channelRead(ctx: ChannelHandlerContext, data: NIOAny) { 73 | defer { 74 | ctx.fireChannelRead(data) 75 | } 76 | 77 | switch unwrapInboundIn(data) { 78 | case let .head(head): 79 | state.requestReceived(head: head) 80 | case let .body(buffer): 81 | state.bodyReceived(buffer: buffer) 82 | case .end: 83 | let (head, buffer) = state.requestComplete() 84 | 85 | var httpBody: Data? 86 | if var body = buffer { 87 | httpBody = body.readString(length: body.readableBytes)?.data(using: .utf8) 88 | } 89 | 90 | let request = Request(head: head, body: httpBody, ctx: ctx) 91 | 92 | var responseBuffer = ctx.channel.allocator.buffer(capacity: 0) 93 | 94 | let eventLoop = ctx.channel.eventLoop 95 | let response = Response() 96 | 97 | handler(request, response) { 98 | eventLoop.execute { 99 | _ = ctx.channel.write(HTTPServerResponsePart.head( 100 | HTTPResponseHead(version: head.version, status: response.statusCode, headers: response.headers)) 101 | ) 102 | 103 | if let body = response.body { 104 | responseBuffer.write(bytes: body) 105 | } 106 | 107 | _ = ctx.channel.write(HTTPServerResponsePart.body(.byteBuffer(responseBuffer))) 108 | 109 | _ = ctx.channel.writeAndFlush(HTTPServerResponsePart.end(nil)).then { 110 | ctx.channel.close() 111 | } 112 | self.state.responseComplete() 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | extension Server.HTTPHandler { 121 | enum State { 122 | case idle 123 | case ignoringRequest 124 | case receivingRequest(HTTPRequestHead, ByteBuffer?) 125 | case sendingResponse 126 | 127 | mutating func requestReceived(head: HTTPRequestHead) { 128 | guard case .idle = self else { 129 | preconditionFailure("Invalid state for \(#function): \(self)") 130 | } 131 | print("Received request: ", head) 132 | self = .receivingRequest(head, nil) 133 | } 134 | 135 | mutating func bodyReceived(buffer: ByteBuffer) { 136 | var body = buffer 137 | guard case .receivingRequest(let header, var buffer) = self else { 138 | preconditionFailure("Invalid state for \(#function): \(self)") 139 | } 140 | if buffer == nil { 141 | buffer = body 142 | } else { 143 | buffer?.write(buffer: &body) 144 | } 145 | self = .receivingRequest(header, buffer) 146 | } 147 | 148 | mutating func requestComplete() -> (HTTPRequestHead, ByteBuffer?) { 149 | guard case let .receivingRequest(header, buffer) = self else { 150 | preconditionFailure("Invalid state for \(#function): \(self)") 151 | } 152 | if var buffer = buffer { 153 | print("Received body: \(buffer.readString(length: buffer.readableBytes) ?? "nil")") 154 | } 155 | self = .sendingResponse 156 | return (header, buffer) 157 | } 158 | 159 | mutating func responseComplete() { 160 | guard case .sendingResponse = self else { 161 | preconditionFailure("Invalid state for response complete: \(self)") 162 | } 163 | self = .idle 164 | } 165 | } 166 | } 167 | 168 | extension Server.HTTPHandler { 169 | public struct Request { 170 | public let head: HTTPRequestHead 171 | public let body: Data? 172 | public let ctx: ChannelHandlerContext 173 | 174 | public init(head: HTTPRequestHead, body: Data?, ctx: ChannelHandlerContext) { 175 | self.head = head 176 | self.body = body 177 | self.ctx = ctx 178 | } 179 | } 180 | } 181 | 182 | extension Server.HTTPHandler { 183 | public class Response { 184 | // if nil then nothing was ever written to response 185 | internal private(set) var _statusCode: HTTPResponseStatus! 186 | public var statusCode: HTTPResponseStatus { 187 | get { _statusCode ?? .ok } 188 | set { _statusCode = newValue } 189 | } 190 | public var headers: HTTPHeaders = HTTPHeaders() 191 | public var body: Data? 192 | 193 | public func sendJSON(_ statusCode: HTTPResponseStatus, headers: HTTPHeaders = HTTPHeaders(), value: T) throws -> Void { 194 | self.statusCode = statusCode 195 | var headers = headers 196 | headers.replaceOrAdd(name: "Content-Type", value: "application/json; charset=utf-8") 197 | self.headers = headers 198 | self.body = try JSONEncoder().encode(value) 199 | } 200 | 201 | public func sendString(_ statusCode: HTTPResponseStatus, headers: HTTPHeaders = HTTPHeaders(), value: String) -> Void { 202 | self.statusCode = statusCode 203 | var headers = headers 204 | headers.replaceOrAdd(name: "Content-Type", value: "text/html; charset=utf-8") 205 | self.headers = headers 206 | self.body = value.data(using: .utf8) 207 | } 208 | 209 | public func sendString(_ statusCode: HTTPResponseStatus, headers: HTTPHeaders = HTTPHeaders()) -> Void { 210 | sendString(statusCode, headers: headers, value: statusCode.reasonPhrase) 211 | } 212 | } 213 | } 214 | 215 | extension URLRequest { 216 | public init(_ request: Server.HTTPHandler.Request) { 217 | let url = URL(string: request.head.uri)! 218 | 219 | var urlRequest = URLRequest(url: url) 220 | urlRequest.httpMethod = "\(request.head.method)" 221 | urlRequest.allHTTPHeaderFields = request.head.headers.reduce(into: [:]) { (headers, pair) in 222 | headers[pair.name] = pair.value 223 | } 224 | urlRequest.httpBody = request.body 225 | self = urlRequest 226 | } 227 | } 228 | 229 | public typealias Middleware = ( 230 | _ request: Server.HTTPHandler.Request, 231 | _ response: Server.HTTPHandler.Response, 232 | _ next: @escaping () -> Void 233 | ) -> Void 234 | 235 | public let notFound: Middleware = { _, response, next in 236 | response.sendString(.notFound) 237 | next() 238 | } 239 | 240 | /// Middleware that can redirect requests to other middlewares 241 | public func router( 242 | notFound: @escaping Middleware = notFound, 243 | route: @escaping (Server.HTTPHandler.Request) -> Middleware? = { _ in nil } 244 | ) -> Middleware { 245 | return { request, response, next in 246 | (route(request) ?? notFound)(request, response, next) 247 | } 248 | } 249 | 250 | /// Middleware that allows to redirect incomming request and intercept responses 251 | public func redirect( 252 | session: URLSession = URLSession.shared, 253 | cachePolicy: NSURLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData, 254 | request redirect: @escaping (Server.HTTPHandler.Request) -> Server.HTTPHandler.Request, 255 | response intercept: @escaping (Server.HTTPHandler.Response) -> Void = { _ in } 256 | ) -> Middleware { 257 | return { request, response, next in 258 | var redirect = URLRequest(redirect(request)) 259 | redirect.cachePolicy = cachePolicy 260 | 261 | let task = session.dataTask(with: redirect) { data, urlResponse, error in 262 | guard let urlResponse = urlResponse as? HTTPURLResponse else { 263 | return next() 264 | } 265 | 266 | response.statusCode = HTTPResponseStatus(statusCode: urlResponse.statusCode) 267 | response.headers = HTTPHeaders(urlResponse.allHeaderFields.map { ("\($0.key)", "\($0.value)") }) 268 | response.body = data 269 | 270 | intercept(response) 271 | 272 | // compressor will add this header itself, 273 | // so they may be duplicated and decoding on the client side will fail 274 | // see: https://github.com/apple/swift-nio/issues/717 275 | response.headers.remove(name: "Content-Encoding") 276 | 277 | next() 278 | } 279 | task.resume() 280 | } 281 | } 282 | 283 | /// Middleware that delays another middleware 284 | public func delay(_ delay: TimeAmount, middleware: @escaping Middleware) -> Middleware { 285 | return { request, response, next in 286 | _ = request.ctx.eventLoop.scheduleTask(in: delay) { 287 | middleware(request, response, next) 288 | } 289 | } 290 | } 291 | 292 | public func router( 293 | notFound: @escaping Middleware = notFound, 294 | route: @escaping Middleware 295 | ) -> Middleware { 296 | return { request, response, finish in 297 | let next = { 298 | if response._statusCode == nil { 299 | notFound(request, response, finish) 300 | } else { 301 | finish() 302 | } 303 | } 304 | route(request, response, next) 305 | } 306 | } 307 | 308 | public func router( 309 | notFound: @escaping Middleware = notFound, 310 | @RouterBuilder _ router: () -> Middleware 311 | ) -> Middleware { 312 | return SwiftNIOMock.router(notFound: notFound, route: router()) 313 | } 314 | 315 | public func router( 316 | notFound: @escaping Middleware = notFound, 317 | services: [Service] 318 | ) -> Middleware { 319 | return SwiftNIOMock.router(notFound: notFound, route: RouterBuilder.buildBlock(services.flatMap { $0.routers })) 320 | } 321 | -------------------------------------------------------------------------------- /Sources/SwiftNIOMock/Service.swift: -------------------------------------------------------------------------------- 1 | import NIOHTTP1 2 | import URLFormat 3 | 4 | public protocol ServiceProtocol: class {} 5 | 6 | open class Service: ServiceProtocol { 7 | public private(set) var routers: [Middleware] = [] 8 | 9 | public init() {} 10 | public convenience init(@RouterBuilder _ router: () -> Middleware) { 11 | self.init() 12 | self.routers.append(router()) 13 | } 14 | 15 | @discardableResult 16 | public func routes(@RouterBuilder _ router: () -> Middleware) -> Self { 17 | self.routers.append(router()) 18 | return self 19 | } 20 | } 21 | 22 | public let GET = ClosedPathFormat(httpMethod(String(describing: HTTPMethod.GET))) 23 | public let PUT = ClosedPathFormat(httpMethod(String(describing: HTTPMethod.PUT))) 24 | public let POST = ClosedPathFormat(httpMethod(String(describing: HTTPMethod.POST))) 25 | public let PATCH = ClosedPathFormat(httpMethod(String(describing: HTTPMethod.PATCH))) 26 | public let DELETE = ClosedPathFormat(httpMethod(String(describing: HTTPMethod.DELETE))) 27 | 28 | public extension ServiceProtocol where Self: Service { 29 | typealias ServiceRoute = (Self) -> Middleware 30 | 31 | @discardableResult 32 | func routes(@ServiceRouterBuilder _ router: () -> ServiceRoute) -> Self { 33 | return self.routes { 34 | router()(self) 35 | } 36 | } 37 | } 38 | 39 | @_functionBuilder 40 | public struct ServiceRouterBuilder { 41 | public typealias RouteClosure = (S) -> Middleware 42 | 43 | public static func buildBlock(_ items: @escaping RouteClosure) -> (S) -> Middleware { 44 | return buildBlock([items]) 45 | } 46 | 47 | public static func buildBlock(_ items: RouteClosure...) -> (S) -> Middleware { 48 | return buildBlock(items) 49 | } 50 | 51 | public static func buildBlock(_ items: [RouteClosure]) -> (S) -> Middleware { 52 | return { service in 53 | RouterBuilder.buildBlock(items.map { $0(service) }) 54 | } 55 | } 56 | } 57 | 58 | @_functionBuilder 59 | public struct RouterBuilder { 60 | public static func buildBlock(_ items: Middleware...) -> Middleware { 61 | return buildBlock(items) 62 | } 63 | 64 | public static func buildBlock(_ items: [Middleware]) -> Middleware { 65 | let allRoutes = items 66 | return { request, response, finish in 67 | guard !allRoutes.isEmpty else { 68 | return finish() 69 | } 70 | var next: (() -> Void)? = {} 71 | var i = allRoutes.startIndex 72 | next = { 73 | let route = allRoutes[i] 74 | i = allRoutes.index(after: i) 75 | let isLast = i == allRoutes.endIndex 76 | route(request, response, isLast ? finish : next!) 77 | } 78 | next!() 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /SwiftNIOMock.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftNIOMock' 3 | s.version = '0.0.1' 4 | s.license = { :type => 'MIT', :file => 'LICENSE' } 5 | s.summary = 'A web server based on SwiftNIO designed to be used as a mock server in UI automation tests' 6 | s.homepage = 'https://github.com/ilyapuchka/SwiftNIOMock' 7 | s.author = 'Ilya Puchka' 8 | s.source = { :git => 'https://github.com/ilyapuchka/SwiftNIOMock.git', :tag => s.version.to_s } 9 | s.module_name = 'SwiftNIOMock' 10 | s.swift_version = '4.2' 11 | s.cocoapods_version = '>=1.1.0' 12 | s.ios.deployment_target = '10.0' 13 | s.osx.deployment_target = '10.10' 14 | s.tvos.deployment_target = '10.0' 15 | s.source_files = 'Sources/SwiftNIOMock/**/*.{swift}' 16 | s.dependency 'SwiftNIO', '~> 1.11.0' 17 | s.dependency 'SwiftNIOHTTP1', '~> 1.11.0' 18 | end 19 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/CNIOAtomics_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/CNIODarwin_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/CNIOHTTPParser_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/CNIOLinux_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/CNIOSHA1_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/CNIOZlib_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/NIOConcurrencyHelpers_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/NIOHTTP1_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/NIOPriorityQueue_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/NIO_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/SwiftNIOMockTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/SwiftNIOMock_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcodeproj/xcshareddata/xcschemes/SwiftNIOMock-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftNIOMock.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B5BCF86821C954B7009DF652 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BCF86721C954B7009DF652 /* AppDelegate.swift */; }; 11 | B5BCF86A21C954B7009DF652 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BCF86921C954B7009DF652 /* ViewController.swift */; }; 12 | B5BCF86D21C954B7009DF652 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5BCF86B21C954B7009DF652 /* Main.storyboard */; }; 13 | B5BCF86F21C954BA009DF652 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5BCF86E21C954BA009DF652 /* Assets.xcassets */; }; 14 | B5BCF87221C954BA009DF652 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5BCF87021C954BA009DF652 /* LaunchScreen.storyboard */; }; 15 | B5BCF87D21C954BA009DF652 /* SwiftNIOMockExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BCF87C21C954BA009DF652 /* SwiftNIOMockExampleUITests.swift */; }; 16 | B5BCF8D821CC1BF0009DF652 /* Recordings in Resources */ = {isa = PBXBuildFile; fileRef = B5BCF8D721CC1BF0009DF652 /* Recordings */; }; 17 | B5C8724E23EEEBA8000B6633 /* SwiftNIOMock in Frameworks */ = {isa = PBXBuildFile; productRef = B5C8724D23EEEBA8000B6633 /* SwiftNIOMock */; }; 18 | B5F6BB0923EEEAB900062CD0 /* Vinyl in Frameworks */ = {isa = PBXBuildFile; productRef = B5F6BB0823EEEAB900062CD0 /* Vinyl */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | B5BCF87921C954BA009DF652 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = B5BCF85C21C954B7009DF652 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = B5BCF86321C954B7009DF652; 27 | remoteInfo = SwiftNIOMockExample; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | B5BCF86421C954B7009DF652 /* SwiftNIOMockExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftNIOMockExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | B5BCF86721C954B7009DF652 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 34 | B5BCF86921C954B7009DF652 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 35 | B5BCF86C21C954B7009DF652 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | B5BCF86E21C954BA009DF652 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | B5BCF87121C954BA009DF652 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | B5BCF87321C954BA009DF652 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | B5BCF87821C954BA009DF652 /* SwiftNIOMockExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftNIOMockExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | B5BCF87C21C954BA009DF652 /* SwiftNIOMockExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftNIOMockExampleUITests.swift; sourceTree = ""; }; 41 | B5BCF87E21C954BA009DF652 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | B5BCF8D721CC1BF0009DF652 /* Recordings */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Recordings; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | B5BCF86121C954B7009DF652 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | B5BCF87521C954BA009DF652 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | B5F6BB0923EEEAB900062CD0 /* Vinyl in Frameworks */, 58 | B5C8724E23EEEBA8000B6633 /* SwiftNIOMock in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | B5BCF85B21C954B7009DF652 = { 66 | isa = PBXGroup; 67 | children = ( 68 | B5BCF86621C954B7009DF652 /* SwiftNIOMockExample */, 69 | B5BCF87B21C954BA009DF652 /* SwiftNIOMockExampleUITests */, 70 | B5BCF86521C954B7009DF652 /* Products */, 71 | B5BCF8A121C95545009DF652 /* Frameworks */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | B5BCF86521C954B7009DF652 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | B5BCF86421C954B7009DF652 /* SwiftNIOMockExample.app */, 79 | B5BCF87821C954BA009DF652 /* SwiftNIOMockExampleUITests.xctest */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | B5BCF86621C954B7009DF652 /* SwiftNIOMockExample */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | B5BCF86721C954B7009DF652 /* AppDelegate.swift */, 88 | B5BCF86921C954B7009DF652 /* ViewController.swift */, 89 | B5BCF86B21C954B7009DF652 /* Main.storyboard */, 90 | B5BCF86E21C954BA009DF652 /* Assets.xcassets */, 91 | B5BCF87021C954BA009DF652 /* LaunchScreen.storyboard */, 92 | B5BCF87321C954BA009DF652 /* Info.plist */, 93 | ); 94 | path = SwiftNIOMockExample; 95 | sourceTree = ""; 96 | }; 97 | B5BCF87B21C954BA009DF652 /* SwiftNIOMockExampleUITests */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | B5BCF87C21C954BA009DF652 /* SwiftNIOMockExampleUITests.swift */, 101 | B5BCF8D721CC1BF0009DF652 /* Recordings */, 102 | B5BCF87E21C954BA009DF652 /* Info.plist */, 103 | ); 104 | path = SwiftNIOMockExampleUITests; 105 | sourceTree = ""; 106 | }; 107 | B5BCF8A121C95545009DF652 /* Frameworks */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | ); 111 | name = Frameworks; 112 | sourceTree = ""; 113 | }; 114 | /* End PBXGroup section */ 115 | 116 | /* Begin PBXNativeTarget section */ 117 | B5BCF86321C954B7009DF652 /* SwiftNIOMockExample */ = { 118 | isa = PBXNativeTarget; 119 | buildConfigurationList = B5BCF88121C954BA009DF652 /* Build configuration list for PBXNativeTarget "SwiftNIOMockExample" */; 120 | buildPhases = ( 121 | B5BCF86021C954B7009DF652 /* Sources */, 122 | B5BCF86121C954B7009DF652 /* Frameworks */, 123 | B5BCF86221C954B7009DF652 /* Resources */, 124 | ); 125 | buildRules = ( 126 | ); 127 | dependencies = ( 128 | ); 129 | name = SwiftNIOMockExample; 130 | productName = SwiftNIOMockExample; 131 | productReference = B5BCF86421C954B7009DF652 /* SwiftNIOMockExample.app */; 132 | productType = "com.apple.product-type.application"; 133 | }; 134 | B5BCF87721C954BA009DF652 /* SwiftNIOMockExampleUITests */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = B5BCF88421C954BA009DF652 /* Build configuration list for PBXNativeTarget "SwiftNIOMockExampleUITests" */; 137 | buildPhases = ( 138 | B5BCF87421C954BA009DF652 /* Sources */, 139 | B5BCF87521C954BA009DF652 /* Frameworks */, 140 | B5BCF87621C954BA009DF652 /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | B5BCF87A21C954BA009DF652 /* PBXTargetDependency */, 146 | ); 147 | name = SwiftNIOMockExampleUITests; 148 | packageProductDependencies = ( 149 | B5F6BB0823EEEAB900062CD0 /* Vinyl */, 150 | B5C8724D23EEEBA8000B6633 /* SwiftNIOMock */, 151 | ); 152 | productName = SwiftNIOMockExampleUITests; 153 | productReference = B5BCF87821C954BA009DF652 /* SwiftNIOMockExampleUITests.xctest */; 154 | productType = "com.apple.product-type.bundle.ui-testing"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | B5BCF85C21C954B7009DF652 /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastSwiftUpdateCheck = 1010; 163 | LastUpgradeCheck = 1010; 164 | ORGANIZATIONNAME = "Ilya Puchka"; 165 | TargetAttributes = { 166 | B5BCF86321C954B7009DF652 = { 167 | CreatedOnToolsVersion = 10.1; 168 | }; 169 | B5BCF87721C954BA009DF652 = { 170 | CreatedOnToolsVersion = 10.1; 171 | TestTargetID = B5BCF86321C954B7009DF652; 172 | }; 173 | }; 174 | }; 175 | buildConfigurationList = B5BCF85F21C954B7009DF652 /* Build configuration list for PBXProject "SwiftNIOMockExample" */; 176 | compatibilityVersion = "Xcode 9.3"; 177 | developmentRegion = en; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | Base, 182 | ); 183 | mainGroup = B5BCF85B21C954B7009DF652; 184 | packageReferences = ( 185 | B5F6BB0723EEEAB900062CD0 /* XCRemoteSwiftPackageReference "Vinyl" */, 186 | B5C8724C23EEEBA8000B6633 /* XCRemoteSwiftPackageReference "SwiftNIOMock" */, 187 | ); 188 | productRefGroup = B5BCF86521C954B7009DF652 /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | B5BCF86321C954B7009DF652 /* SwiftNIOMockExample */, 193 | B5BCF87721C954BA009DF652 /* SwiftNIOMockExampleUITests */, 194 | ); 195 | }; 196 | /* End PBXProject section */ 197 | 198 | /* Begin PBXResourcesBuildPhase section */ 199 | B5BCF86221C954B7009DF652 /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | B5BCF87221C954BA009DF652 /* LaunchScreen.storyboard in Resources */, 204 | B5BCF86F21C954BA009DF652 /* Assets.xcassets in Resources */, 205 | B5BCF86D21C954B7009DF652 /* Main.storyboard in Resources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | B5BCF87621C954BA009DF652 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | B5BCF8D821CC1BF0009DF652 /* Recordings in Resources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXResourcesBuildPhase section */ 218 | 219 | /* Begin PBXSourcesBuildPhase section */ 220 | B5BCF86021C954B7009DF652 /* Sources */ = { 221 | isa = PBXSourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | B5BCF86A21C954B7009DF652 /* ViewController.swift in Sources */, 225 | B5BCF86821C954B7009DF652 /* AppDelegate.swift in Sources */, 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | B5BCF87421C954BA009DF652 /* Sources */ = { 230 | isa = PBXSourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | B5BCF87D21C954BA009DF652 /* SwiftNIOMockExampleUITests.swift in Sources */, 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | /* End PBXSourcesBuildPhase section */ 238 | 239 | /* Begin PBXTargetDependency section */ 240 | B5BCF87A21C954BA009DF652 /* PBXTargetDependency */ = { 241 | isa = PBXTargetDependency; 242 | target = B5BCF86321C954B7009DF652 /* SwiftNIOMockExample */; 243 | targetProxy = B5BCF87921C954BA009DF652 /* PBXContainerItemProxy */; 244 | }; 245 | /* End PBXTargetDependency section */ 246 | 247 | /* Begin PBXVariantGroup section */ 248 | B5BCF86B21C954B7009DF652 /* Main.storyboard */ = { 249 | isa = PBXVariantGroup; 250 | children = ( 251 | B5BCF86C21C954B7009DF652 /* Base */, 252 | ); 253 | name = Main.storyboard; 254 | sourceTree = ""; 255 | }; 256 | B5BCF87021C954BA009DF652 /* LaunchScreen.storyboard */ = { 257 | isa = PBXVariantGroup; 258 | children = ( 259 | B5BCF87121C954BA009DF652 /* Base */, 260 | ); 261 | name = LaunchScreen.storyboard; 262 | sourceTree = ""; 263 | }; 264 | /* End PBXVariantGroup section */ 265 | 266 | /* Begin XCBuildConfiguration section */ 267 | B5BCF87F21C954BA009DF652 /* Debug */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_ENABLE_OBJC_WEAK = YES; 278 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 279 | CLANG_WARN_BOOL_CONVERSION = YES; 280 | CLANG_WARN_COMMA = YES; 281 | CLANG_WARN_CONSTANT_CONVERSION = YES; 282 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 283 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 284 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INFINITE_RECURSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 291 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 294 | CLANG_WARN_STRICT_PROTOTYPES = YES; 295 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 296 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 297 | CLANG_WARN_UNREACHABLE_CODE = YES; 298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 299 | CODE_SIGN_IDENTITY = "iPhone Developer"; 300 | COPY_PHASE_STRIP = NO; 301 | DEBUG_INFORMATION_FORMAT = dwarf; 302 | ENABLE_STRICT_OBJC_MSGSEND = YES; 303 | ENABLE_TESTABILITY = YES; 304 | GCC_C_LANGUAGE_STANDARD = gnu11; 305 | GCC_DYNAMIC_NO_PIC = NO; 306 | GCC_NO_COMMON_BLOCKS = YES; 307 | GCC_OPTIMIZATION_LEVEL = 0; 308 | GCC_PREPROCESSOR_DEFINITIONS = ( 309 | "DEBUG=1", 310 | "$(inherited)", 311 | ); 312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 314 | GCC_WARN_UNDECLARED_SELECTOR = YES; 315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 316 | GCC_WARN_UNUSED_FUNCTION = YES; 317 | GCC_WARN_UNUSED_VARIABLE = YES; 318 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 319 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 320 | MTL_FAST_MATH = YES; 321 | ONLY_ACTIVE_ARCH = YES; 322 | SDKROOT = iphoneos; 323 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 324 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 325 | }; 326 | name = Debug; 327 | }; 328 | B5BCF88021C954BA009DF652 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_ANALYZER_NONNULL = YES; 333 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 335 | CLANG_CXX_LIBRARY = "libc++"; 336 | CLANG_ENABLE_MODULES = YES; 337 | CLANG_ENABLE_OBJC_ARC = YES; 338 | CLANG_ENABLE_OBJC_WEAK = YES; 339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_COMMA = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 345 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 346 | CLANG_WARN_EMPTY_BODY = YES; 347 | CLANG_WARN_ENUM_CONVERSION = YES; 348 | CLANG_WARN_INFINITE_RECURSION = YES; 349 | CLANG_WARN_INT_CONVERSION = YES; 350 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 351 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | CODE_SIGN_IDENTITY = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 363 | ENABLE_NS_ASSERTIONS = NO; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu11; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 374 | MTL_ENABLE_DEBUG_INFO = NO; 375 | MTL_FAST_MATH = YES; 376 | SDKROOT = iphoneos; 377 | SWIFT_COMPILATION_MODE = wholemodule; 378 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 379 | VALIDATE_PRODUCT = YES; 380 | }; 381 | name = Release; 382 | }; 383 | B5BCF88221C954BA009DF652 /* Debug */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | CODE_SIGN_STYLE = Automatic; 388 | INFOPLIST_FILE = SwiftNIOMockExample/Info.plist; 389 | LD_RUNPATH_SEARCH_PATHS = ( 390 | "$(inherited)", 391 | "@executable_path/Frameworks", 392 | ); 393 | PRODUCT_BUNDLE_IDENTIFIER = ilya.puchka.SwiftNIOMockExample; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SWIFT_VERSION = 4.2; 396 | TARGETED_DEVICE_FAMILY = "1,2"; 397 | }; 398 | name = Debug; 399 | }; 400 | B5BCF88321C954BA009DF652 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 404 | CODE_SIGN_STYLE = Automatic; 405 | INFOPLIST_FILE = SwiftNIOMockExample/Info.plist; 406 | LD_RUNPATH_SEARCH_PATHS = ( 407 | "$(inherited)", 408 | "@executable_path/Frameworks", 409 | ); 410 | PRODUCT_BUNDLE_IDENTIFIER = ilya.puchka.SwiftNIOMockExample; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SWIFT_VERSION = 4.2; 413 | TARGETED_DEVICE_FAMILY = "1,2"; 414 | }; 415 | name = Release; 416 | }; 417 | B5BCF88521C954BA009DF652 /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | CODE_SIGN_STYLE = Automatic; 421 | INFOPLIST_FILE = SwiftNIOMockExampleUITests/Info.plist; 422 | LD_RUNPATH_SEARCH_PATHS = ( 423 | "$(inherited)", 424 | "@executable_path/Frameworks", 425 | "@loader_path/Frameworks", 426 | ); 427 | PRODUCT_BUNDLE_IDENTIFIER = ilya.puchka.SwiftNIOMockExampleUITests; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SWIFT_VERSION = 4.2; 430 | TARGETED_DEVICE_FAMILY = "1,2"; 431 | TEST_TARGET_NAME = SwiftNIOMockExample; 432 | }; 433 | name = Debug; 434 | }; 435 | B5BCF88621C954BA009DF652 /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | CODE_SIGN_STYLE = Automatic; 439 | INFOPLIST_FILE = SwiftNIOMockExampleUITests/Info.plist; 440 | LD_RUNPATH_SEARCH_PATHS = ( 441 | "$(inherited)", 442 | "@executable_path/Frameworks", 443 | "@loader_path/Frameworks", 444 | ); 445 | PRODUCT_BUNDLE_IDENTIFIER = ilya.puchka.SwiftNIOMockExampleUITests; 446 | PRODUCT_NAME = "$(TARGET_NAME)"; 447 | SWIFT_VERSION = 4.2; 448 | TARGETED_DEVICE_FAMILY = "1,2"; 449 | TEST_TARGET_NAME = SwiftNIOMockExample; 450 | }; 451 | name = Release; 452 | }; 453 | /* End XCBuildConfiguration section */ 454 | 455 | /* Begin XCConfigurationList section */ 456 | B5BCF85F21C954B7009DF652 /* Build configuration list for PBXProject "SwiftNIOMockExample" */ = { 457 | isa = XCConfigurationList; 458 | buildConfigurations = ( 459 | B5BCF87F21C954BA009DF652 /* Debug */, 460 | B5BCF88021C954BA009DF652 /* Release */, 461 | ); 462 | defaultConfigurationIsVisible = 0; 463 | defaultConfigurationName = Release; 464 | }; 465 | B5BCF88121C954BA009DF652 /* Build configuration list for PBXNativeTarget "SwiftNIOMockExample" */ = { 466 | isa = XCConfigurationList; 467 | buildConfigurations = ( 468 | B5BCF88221C954BA009DF652 /* Debug */, 469 | B5BCF88321C954BA009DF652 /* Release */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | B5BCF88421C954BA009DF652 /* Build configuration list for PBXNativeTarget "SwiftNIOMockExampleUITests" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | B5BCF88521C954BA009DF652 /* Debug */, 478 | B5BCF88621C954BA009DF652 /* Release */, 479 | ); 480 | defaultConfigurationIsVisible = 0; 481 | defaultConfigurationName = Release; 482 | }; 483 | /* End XCConfigurationList section */ 484 | 485 | /* Begin XCRemoteSwiftPackageReference section */ 486 | B5C8724C23EEEBA8000B6633 /* XCRemoteSwiftPackageReference "SwiftNIOMock" */ = { 487 | isa = XCRemoteSwiftPackageReference; 488 | repositoryURL = "../"; 489 | requirement = { 490 | branch = master; 491 | kind = branch; 492 | }; 493 | }; 494 | B5F6BB0723EEEAB900062CD0 /* XCRemoteSwiftPackageReference "Vinyl" */ = { 495 | isa = XCRemoteSwiftPackageReference; 496 | repositoryURL = "https://github.com/Velhotes/Vinyl"; 497 | requirement = { 498 | branch = master; 499 | kind = branch; 500 | }; 501 | }; 502 | /* End XCRemoteSwiftPackageReference section */ 503 | 504 | /* Begin XCSwiftPackageProductDependency section */ 505 | B5C8724D23EEEBA8000B6633 /* SwiftNIOMock */ = { 506 | isa = XCSwiftPackageProductDependency; 507 | package = B5C8724C23EEEBA8000B6633 /* XCRemoteSwiftPackageReference "SwiftNIOMock" */; 508 | productName = SwiftNIOMock; 509 | }; 510 | B5F6BB0823EEEAB900062CD0 /* Vinyl */ = { 511 | isa = XCSwiftPackageProductDependency; 512 | package = B5F6BB0723EEEAB900062CD0 /* XCRemoteSwiftPackageReference "Vinyl" */; 513 | productName = Vinyl; 514 | }; 515 | /* End XCSwiftPackageProductDependency section */ 516 | }; 517 | rootObject = B5BCF85C21C954B7009DF652 /* Project object */; 518 | } 519 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Chalk", 6 | "repositoryURL": "https://github.com/mxcl/Chalk.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "9aa9f348b86db3cf6702a3e43c081ecec80cf3c7", 10 | "version": "0.4.0" 11 | } 12 | }, 13 | { 14 | "package": "CommonParsers", 15 | "repositoryURL": "https://github.com/ilyapuchka/common-parsers.git", 16 | "state": { 17 | "branch": "master", 18 | "revision": "2386ef5d444746ad77a70296d878683ae0f9c9a2", 19 | "version": null 20 | } 21 | }, 22 | { 23 | "package": "FileCheck", 24 | "repositoryURL": "https://github.com/llvm-swift/FileCheck.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "9ed91cc30a1a325d989fac117f3f3065a16b9f76", 28 | "version": "0.2.3" 29 | } 30 | }, 31 | { 32 | "package": "swift-nio", 33 | "repositoryURL": "https://github.com/apple/swift-nio.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd", 37 | "version": "1.14.1" 38 | } 39 | }, 40 | { 41 | "package": "swift-nio-zlib-support", 42 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", 46 | "version": "1.0.0" 47 | } 48 | }, 49 | { 50 | "package": "Prelude", 51 | "repositoryURL": "https://github.com/pointfreeco/swift-prelude.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "9240a1f951c382e1c7387762c6cd94042baba531", 55 | "version": null 56 | } 57 | }, 58 | { 59 | "package": "swift-tools-support-core", 60 | "repositoryURL": "https://github.com/apple/swift-tools-support-core.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "693aba4c4c9dcc4767cc853a0dd38bf90ad8c258", 64 | "version": "0.0.1" 65 | } 66 | }, 67 | { 68 | "package": "SwiftCheck", 69 | "repositoryURL": "https://github.com/typelift/SwiftCheck", 70 | "state": { 71 | "branch": null, 72 | "revision": "077c096c3ddfc38db223ac8e525ad16ffb987138", 73 | "version": "0.12.0" 74 | } 75 | }, 76 | { 77 | "package": "SwiftNIOMock", 78 | "repositoryURL": "/Users/ilya.puchka/dev/SwiftNIOMock", 79 | "state": { 80 | "branch": "master", 81 | "revision": "2df9f947fb0041c5be6cbb7b1397b2c9a0fe6857", 82 | "version": null 83 | } 84 | }, 85 | { 86 | "package": "URLFormat", 87 | "repositoryURL": "https://github.com/ilyapuchka/URLFormat.git", 88 | "state": { 89 | "branch": "master", 90 | "revision": "a9674f8869631d393837db6899b4ce8649d4dc84", 91 | "version": null 92 | } 93 | }, 94 | { 95 | "package": "Vinyl", 96 | "repositoryURL": "https://github.com/Velhotes/Vinyl", 97 | "state": { 98 | "branch": "master", 99 | "revision": "ebcec23c80087dc6539b8fb262316b4cfd8e772d", 100 | "version": null 101 | } 102 | } 103 | ] 104 | }, 105 | "version": 1 106 | } 107 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample.xcodeproj/xcshareddata/xcschemes/SwiftNIOMockExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftNIOMockExample 4 | // 5 | // Created by Ilya Puchka on 18/12/2018. 6 | // Copyright © 2018 Ilya Puchka. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftNIOMockExample 4 | // 5 | // Created by Ilya Puchka on 18/12/2018. 6 | // Copyright © 2018 Ilya Puchka. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | RecordingsPath 22 | $(SRCROOT)/SwiftNIOMockExampleUITests/Recordings 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExampleUITests/Recordings/testDelay.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : { 4 | "url" : "https:\/\/postman-echo.com\/get", 5 | "method" : "GET", 6 | "headers" : { 7 | "Host" : "localhost:8080", 8 | "Accept" : "*\/*", 9 | "User-Agent" : "SwiftNIOMockExampleUITests-Runner\/1 CFNetwork\/975.0.3 Darwin\/17.7.0", 10 | "Accept-Encoding" : "gzip, deflate", 11 | "Connection" : "keep-alive", 12 | "Accept-Language" : "en-us" 13 | } 14 | }, 15 | "response" : { 16 | "status" : 200, 17 | "body" : { 18 | "headers" : { 19 | "accept" : "*\/*", 20 | "if-none-match" : "W\/\"1bb-TjVovasPpx9yXC28riuXpSmqHac\"", 21 | "x-forwarded-port" : "443", 22 | "host" : "localhost", 23 | "x-forwarded-proto" : "https", 24 | "accept-language" : "en-us", 25 | "accept-encoding" : "gzip, deflate", 26 | "user-agent" : "SwiftNIOMockExampleUITests-Runner\/1 CFNetwork\/975.0.3 Darwin\/17.7.0" 27 | }, 28 | "args" : { 29 | 30 | }, 31 | "url" : "https:\/\/localhost\/get" 32 | }, 33 | "headers" : { 34 | "Content-Encoding" : "gzip", 35 | "Etag" : "W\/\"153-D\/GcDAZ1WLhMovYNpJFnyvZx\/fs\"", 36 | "Connection" : "keep-alive", 37 | "Content-Type" : "application\/json; charset=utf-8", 38 | "Vary" : "Accept-Encoding", 39 | "Date" : "Thu, 20 Dec 2018 19:34:25 GMT", 40 | "Content-Length" : "268", 41 | "Server" : "nginx", 42 | "Set-Cookie" : "sails.sid=s%3A8e4ywEa-s53qF5JaToxFjA6jT-PFBfpV.iqBucmW7iuuMHDheZ9nMAlBBUIhSYHEsN%2BYdJy7RhDw; Path=\/; HttpOnly" 43 | }, 44 | "url" : "https:\/\/postman-echo.com\/get" 45 | } 46 | } 47 | ] -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExampleUITests/Recordings/testGET.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : { 4 | "url" : "https:\/\/postman-echo.com\/get", 5 | "method" : "GET", 6 | "headers" : { 7 | "Accept" : "*\/*", 8 | "Host" : "localhost:8080", 9 | "Accept-Language" : "en-us", 10 | "Connection" : "keep-alive", 11 | "If-None-Match" : "W\/\"153-D\/GcDAZ1WLhMovYNpJFnyvZx\/fs\"", 12 | "Accept-Encoding" : "gzip, deflate", 13 | "User-Agent" : "SwiftNIOMockExampleUITests-Runner\/1 CFNetwork\/975.0.3 Darwin\/17.7.0", 14 | "Cookie" : "sails.sid=s%3A8e4ywEa-s53qF5JaToxFjA6jT-PFBfpV.iqBucmW7iuuMHDheZ9nMAlBBUIhSYHEsN%2BYdJy7RhDw" 15 | } 16 | }, 17 | "response" : { 18 | "status" : 200, 19 | "body" : { 20 | "headers" : { 21 | "accept" : "*\/*", 22 | "cookie" : "sails.sid=s%3A8e4ywEa-s53qF5JaToxFjA6jT-PFBfpV.iqBucmW7iuuMHDheZ9nMAlBBUIhSYHEsN%2BYdJy7RhDw", 23 | "if-none-match" : "W\/\"153-D\/GcDAZ1WLhMovYNpJFnyvZx\/fs\"", 24 | "x-forwarded-port" : "443", 25 | "host" : "localhost", 26 | "x-forwarded-proto" : "https", 27 | "accept-language" : "en-us", 28 | "accept-encoding" : "gzip, deflate", 29 | "user-agent" : "SwiftNIOMockExampleUITests-Runner\/1 CFNetwork\/975.0.3 Darwin\/17.7.0" 30 | }, 31 | "args" : { 32 | 33 | }, 34 | "url" : "https:\/\/localhost\/get" 35 | }, 36 | "headers" : { 37 | "Connection" : "keep-alive", 38 | "Etag" : "W\/\"1bb-SX48QhT\/15iowa3VFuqXZ+q\/VRI\"", 39 | "Content-Type" : "application\/json; charset=utf-8", 40 | "Date" : "Thu, 20 Dec 2018 19:34:32 GMT", 41 | "Vary" : "Accept-Encoding", 42 | "Content-Length" : "354", 43 | "Server" : "nginx", 44 | "Content-Encoding" : "gzip" 45 | }, 46 | "url" : "https:\/\/postman-echo.com\/get" 47 | } 48 | } 49 | ] -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExampleUITests/Recordings/testPOST.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : { 4 | "method" : "POST", 5 | "body" : "Hello world", 6 | "headers" : { 7 | "Accept" : "*\/*", 8 | "Host" : "localhost:8080", 9 | "Connection" : "keep-alive", 10 | "Accept-Language" : "en-us", 11 | "If-None-Match" : "W\/\"1bb-SX48QhT\/15iowa3VFuqXZ+q\/VRI\"", 12 | "Content-Type" : "text\/html; charset=utf-8", 13 | "Accept-Encoding" : "gzip, deflate", 14 | "User-Agent" : "SwiftNIOMockExampleUITests-Runner\/1 CFNetwork\/975.0.3 Darwin\/17.7.0", 15 | "Content-Length" : "11", 16 | "Cookie" : "sails.sid=s%3A8e4ywEa-s53qF5JaToxFjA6jT-PFBfpV.iqBucmW7iuuMHDheZ9nMAlBBUIhSYHEsN%2BYdJy7RhDw" 17 | }, 18 | "url" : "https:\/\/postman-echo.com\/post" 19 | }, 20 | "response" : { 21 | "status" : 200, 22 | "body" : { 23 | "json" : null, 24 | "url" : "https:\/\/localhost\/post", 25 | "data" : "Hello world", 26 | "headers" : { 27 | "accept" : "*\/*", 28 | "content-type" : "text\/html; charset=utf-8", 29 | "cookie" : "sails.sid=s%3A8e4ywEa-s53qF5JaToxFjA6jT-PFBfpV.iqBucmW7iuuMHDheZ9nMAlBBUIhSYHEsN%2BYdJy7RhDw", 30 | "user-agent" : "SwiftNIOMockExampleUITests-Runner\/1 CFNetwork\/975.0.3 Darwin\/17.7.0", 31 | "if-none-match" : "W\/\"1bb-SX48QhT\/15iowa3VFuqXZ+q\/VRI\"", 32 | "x-forwarded-port" : "443", 33 | "host" : "localhost", 34 | "x-forwarded-proto" : "https", 35 | "content-length" : "11", 36 | "accept-encoding" : "gzip, deflate", 37 | "accept-language" : "en-us" 38 | }, 39 | "args" : { 40 | 41 | }, 42 | "files" : { 43 | 44 | }, 45 | "form" : { 46 | 47 | } 48 | }, 49 | "headers" : { 50 | "Content-Encoding" : "gzip", 51 | "Etag" : "W\/\"232-cbBU4vH3EPng6\/bwfYkSAWeA06E\"", 52 | "Connection" : "keep-alive", 53 | "Content-Type" : "application\/json; charset=utf-8", 54 | "Vary" : "Accept-Encoding", 55 | "Date" : "Thu, 20 Dec 2018 19:34:39 GMT", 56 | "Content-Length" : "426", 57 | "Server" : "nginx", 58 | "Set-Cookie" : "sails.sid=s%3AGesyRZ19NLJHV7Pp2uzRF72PCm0-B2Wh.ji%2FUcCckMVk%2BkS3fYJxESrspbzu54k7K9k30zAHf5Og; Path=\/; HttpOnly" 59 | }, 60 | "url" : "https:\/\/postman-echo.com\/post" 61 | } 62 | } 63 | ] -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExampleUITests/Recordings/testRoute.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /SwiftNIOMockExample/SwiftNIOMockExampleUITests/SwiftNIOMockExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftNIOMockExampleUITests.swift 3 | // SwiftNIOMockExampleUITests 4 | // 5 | // Created by Ilya Puchka on 18/12/2018. 6 | // Copyright © 2018 Ilya Puchka. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftNIOMock 11 | import Vinyl 12 | import URLFormat 13 | 14 | class SwiftNIOMockExampleUITests: XCTestCase { 15 | var server: Server! 16 | var notFound: Middleware! 17 | var recordingSession: Turntable? 18 | 19 | func setupRecording(_ shouldRecord: Bool) { 20 | let testName = String(describing: self.invocation!.selector) 21 | 22 | let bundle = Bundle(for: SwiftNIOMockExampleUITests.self) 23 | let recordingPath = bundle.infoDictionary!["RecordingsPath"] as! NSString 24 | let recordingFilePath = "\(recordingPath)/\(testName).json" 25 | let vinylName = "\(recordingPath.lastPathComponent)/\(testName)" 26 | 27 | let recordingMode = shouldRecord 28 | ? RecordingMode.missingVinyl(recordingPath: recordingFilePath) 29 | : RecordingMode.none 30 | 31 | let matchingStrategy = MatchingStrategy.requestAttributes( 32 | types: [.method, .host, .path, .query, .body], 33 | playTracksUniquely: false 34 | ) 35 | let configuration = TurntableConfiguration( 36 | matchingStrategy: matchingStrategy, 37 | recordingMode: recordingMode 38 | ) 39 | recordingSession = Turntable(vinylName: vinylName, turntableConfiguration: configuration) 40 | } 41 | 42 | func setupMockServer() { 43 | notFound = SwiftNIOMock.redirect( 44 | session: recordingSession ?? URLSession.shared, 45 | request: { (request) in 46 | var head = request.head 47 | head.uri = "https://postman-echo.com/\(String(describing: request.head.method).lowercased())" 48 | return Server.HTTPHandler.Request(head: head, body: request.body, ctx: request.ctx) 49 | }, response: { (response) in 50 | let string = String(data: response.body ?? Data(), encoding: .utf8)! 51 | response.sendString(response.statusCode, value: "This response was intercepted!\r\n" + string) 52 | }) 53 | 54 | server = Server(port: 8080, handler: 55 | SwiftNIOMock.router( 56 | notFound: notFound, 57 | services: [ 58 | Service { 59 | route(GET/.helloworld) { (_, response, next) in 60 | response.sendString(.ok, value: "Hello world!") 61 | next() 62 | } 63 | } 64 | ] 65 | ) 66 | ) 67 | try! server.start() 68 | } 69 | 70 | override func setUp() { 71 | continueAfterFailure = false 72 | 73 | setupRecording(true) 74 | setupMockServer() 75 | 76 | XCUIApplication().launch() 77 | } 78 | 79 | override func tearDown() { 80 | recordingSession?.stopRecording() 81 | try! server.stop() 82 | server = nil 83 | } 84 | 85 | func testGET() { 86 | let exp = expectation(description: "Recieved response") 87 | var request = URLRequest(url: URL(string: "http://localhost:8080")!) 88 | request.httpMethod = "GET" 89 | URLSession.shared.dataTask(with: request) { (data, response, error) in 90 | print(String(data: data ?? Data(), encoding: .utf8) ?? "nil") 91 | print(response ?? "nil") 92 | print(error ?? "nil") 93 | if response != nil { 94 | exp.fulfill() 95 | } 96 | }.resume() 97 | wait(for: [exp], timeout: 5) 98 | } 99 | 100 | func testPOST() { 101 | let exp = expectation(description: "Recieved response") 102 | var request = URLRequest(url: URL(string: "http://localhost:8080")!) 103 | request.httpMethod = "POST" 104 | request.httpBody = "Hello world".data(using: .utf8) 105 | request.setValue("text/html; charset=utf-8", forHTTPHeaderField: "Content-Type") 106 | URLSession.shared.dataTask(with: request) { (data, response, error) in 107 | print(String(data: data ?? Data(), encoding: .utf8) ?? "nil") 108 | print(response ?? "nil") 109 | print(error ?? "nil") 110 | if response != nil { 111 | exp.fulfill() 112 | } 113 | }.resume() 114 | wait(for: [exp], timeout: 5) 115 | } 116 | 117 | func testRoute() { 118 | let exp = expectation(description: "Recieved response") 119 | var request = URLRequest(url: URL(string: "http://localhost:8080/helloworld")!) 120 | request.httpMethod = "GET" 121 | URLSession.shared.dataTask(with: request) { (data, response, error) in 122 | print(String(data: data ?? Data(), encoding: .utf8) ?? "nil") 123 | print(response ?? "nil") 124 | print(error ?? "nil") 125 | if response != nil { 126 | exp.fulfill() 127 | } 128 | }.resume() 129 | wait(for: [exp], timeout: 5) 130 | } 131 | 132 | func testDelay() { 133 | try! server.stop() 134 | 135 | server = Server(port: 8080, handler: SwiftNIOMock.delay(.seconds(2), middleware: notFound)) 136 | try! server.start() 137 | 138 | let exp = expectation(description: "Recieved response") 139 | var request = URLRequest(url: URL(string: "http://localhost:8080")!) 140 | request.httpMethod = "GET" 141 | URLSession.shared.dataTask(with: request) { (data, response, error) in 142 | print(String(data: data ?? Data(), encoding: .utf8) ?? "nil") 143 | print(response ?? "nil") 144 | print(error ?? "nil") 145 | if response != nil { 146 | exp.fulfill() 147 | } 148 | }.resume() 149 | wait(for: [exp], timeout: 5) 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftNIOMockTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftNIOMockTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /Tests/SwiftNIOMockTests/SwiftNIOMockTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftNIOMock 3 | import NIOHTTP1 4 | import URLFormat 5 | import CommonParsers 6 | import Prelude 7 | 8 | class SwiftNIOMockTests: XCTestCase { 9 | func withServer( 10 | handler: @escaping Middleware = { _, _, next in next() }, 11 | expect: String = "", 12 | assert: (Server, XCTestExpectation) -> Void 13 | ) { 14 | let server = Server(port: 8080, handler: handler) 15 | try! server.start() 16 | defer { try! server.stop() } 17 | 18 | let expectation = self.expectation(description: expect) 19 | assert(server, expectation) 20 | waitForExpectations(timeout: 5, handler: nil) 21 | } 22 | 23 | func testCanRestartServer() { 24 | // given server 25 | let server = Server(port: 8080) 26 | 27 | // when started 28 | try! server.start() 29 | 30 | // then should beb stopped to restart 31 | XCTAssertThrowsError(try server.start(), "Should not start server without stopping it first") 32 | 33 | // when stopped 34 | try! server.stop() 35 | 36 | // then can restart 37 | XCTAssertNoThrow(try server.start(), "Server should start again after being stopped") 38 | try! server.stop() 39 | } 40 | 41 | func testCanRunTwoServersOnDifferentPorts() { 42 | // given server1 43 | let server1 = Server(port: 8080) 44 | // given server2 45 | let server2 = Server(port: 8081) 46 | 47 | // when started 1 48 | try! server1.start() 49 | 50 | //then can start another server on another port 51 | XCTAssertNoThrow(try server2.start(), "Second server should be started on another port") 52 | 53 | try! server1.stop() 54 | try! server2.stop() 55 | } 56 | 57 | func testCanReturnDefaultResponse() { 58 | // given server with empty handler 59 | withServer { server, expectation in 60 | // when making a request 61 | let url = URL(string: "http://localhost:8080")! 62 | URLSession.shared.dataTask(with: url) { data, response, error in 63 | defer { expectation.fulfill() } 64 | 65 | // expect to recieve default response 66 | XCTAssertNil(error) 67 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 68 | XCTAssertTrue(data?.count == 0) 69 | }.resume() 70 | } 71 | } 72 | 73 | func testCanRedirectRequestAndInterceptResponse() { 74 | // given server configured with redirect 75 | let calledRedirect = self.expectation(description: "") 76 | let redirectRequest = { (request: Server.HTTPHandler.Request) -> Server.HTTPHandler.Request in 77 | defer { calledRedirect.fulfill() } 78 | 79 | var head = request.head 80 | head.headers.add(name: "custom-request-header", value: "custom-request-header-value") 81 | 82 | var components = URLComponents(string: head.uri)! 83 | components.host = "postman-echo.com" 84 | components.path = "/\(String(describing: request.head.method).lowercased())" 85 | components.scheme = "https" 86 | head.uri = components.url!.absoluteString 87 | 88 | return Server.HTTPHandler.Request(head: head, body: request.body, ctx: request.ctx) 89 | } 90 | 91 | let calledIntercept = self.expectation(description: "") 92 | var originalResponse: HTTPResponseHead! 93 | let interceptResponse = { (response: Server.HTTPHandler.Response) in 94 | defer { calledIntercept.fulfill() } 95 | 96 | response.statusCode = .created 97 | response.headers.add(name: "custom-response-header", value: "custom-response-header-value") 98 | 99 | response.body = response.body 100 | .flatMap { try? JSONSerialization.jsonObject(with: $0, options: []) } 101 | .flatMap { try? JSONSerialization.data(withJSONObject: ["response": $0], options: []) } 102 | 103 | originalResponse = HTTPResponseHead( 104 | version: HTTPVersion(major: 1, minor: 1), 105 | status: response.statusCode, 106 | headers: response.headers 107 | ) 108 | } 109 | let redirect = SwiftNIOMock.redirect( 110 | request: redirectRequest, 111 | response: interceptResponse 112 | ) 113 | 114 | struct EchoData: Decodable, Equatable { 115 | let args: [String: String] 116 | let data: String 117 | let headers: [String: String] 118 | let url: String 119 | } 120 | struct ResponseJSON: Decodable, Equatable { 121 | let response: EchoData 122 | } 123 | // expect to recieve intercepted response 124 | let expectedResponseJSON = ResponseJSON(response: EchoData( 125 | args: ["query": "value"], 126 | data: "Hello world!", 127 | headers: [ 128 | "accept": "*/*", 129 | "accept-encoding": "gzip", 130 | "accept-language": "en-gb", 131 | "cache-control": "no-cache", 132 | "content-length": "12", 133 | "content-type": "text/html; charset=utf-8", 134 | "custom-request-header": "custom-request-header-value", 135 | "host": "localhost", 136 | "user-agent": "xctest", 137 | "x-forwarded-port": "443", 138 | "x-forwarded-proto": "https" 139 | ], 140 | url: "https://localhost/post?query=value" 141 | )) 142 | 143 | let url = URL(string: "http://localhost:8080?query=value")! 144 | var request = URLRequest(url: url) 145 | request.httpMethod = "POST" 146 | request.httpBody = "Hello world!".data(using: .utf8) 147 | request.setValue("text/html; charset=utf-8", forHTTPHeaderField: "Content-Type") 148 | request.setValue("xctest", forHTTPHeaderField: "User-Agent") 149 | request.setValue("en-gb", forHTTPHeaderField: "Accept-Language") 150 | request.setValue("gzip", forHTTPHeaderField: "Accept-Encoding") 151 | 152 | withServer(handler: redirect) { (server, expectation) in 153 | // when making a request 154 | URLSession.shared.dataTask(with: request) { data, response, error in 155 | defer { expectation.fulfill() } 156 | 157 | let receivedResponseJSON = data.flatMap { try? JSONDecoder().decode(ResponseJSON.self, from: $0) } 158 | 159 | XCTAssertEqual(expectedResponseJSON.response.args, receivedResponseJSON?.response.args) 160 | XCTAssertEqual(expectedResponseJSON.response.data, receivedResponseJSON?.response.data) 161 | XCTAssertEqual(expectedResponseJSON.response.headers, receivedResponseJSON?.response.headers) 162 | XCTAssertEqual(expectedResponseJSON, receivedResponseJSON) 163 | 164 | let httpResponse = response as! HTTPURLResponse 165 | var responseHead = HTTPResponseHead( 166 | version: HTTPVersion(major: 1, minor: 1), 167 | status: HTTPResponseStatus.init(statusCode: httpResponse.statusCode), 168 | headers: HTTPHeaders(httpResponse.allHeaderFields.map { ("\($0.key)", "\($0.value)") }) 169 | ) 170 | 171 | // content lengths and encoding will be different because of compression 172 | // see: https://github.com/apple/swift-nio/issues/717 173 | responseHead.headers.remove(name: "Content-Length") 174 | originalResponse.headers.remove(name: "Content-Length") 175 | responseHead.headers.remove(name: "Content-Encoding") 176 | originalResponse.headers.remove(name: "Content-Encoding") 177 | 178 | XCTAssertEqual(responseHead, originalResponse) 179 | }.resume() 180 | } 181 | } 182 | 183 | func testEmptyRouter() { 184 | // given server with empty router 185 | withServer(handler: router()) { server, expectation in 186 | // when making a request 187 | let url = URL(string: "http://localhost:8080")! 188 | URLSession.shared.dataTask(with: url) { data, response, error in 189 | defer { expectation.fulfill() } 190 | // expect to recieve default not found response 191 | XCTAssertNil(error) 192 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 404) 193 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Not Found") 194 | }.resume() 195 | } 196 | } 197 | 198 | func testNonEmptyRouter() { 199 | // given server with non empty router 200 | let router = SwiftNIOMock.router { 201 | route(GET/.helloworld) { (request, response, next) in 202 | response.sendString(.ok, value: "Hello world!") 203 | next() 204 | } 205 | } 206 | withServer(handler: router) { server, expectation in 207 | defer { expectation.fulfill() } 208 | 209 | // when making request to known route 210 | let knownRouteExpectation = self.expectation(description: "") 211 | let knownUrl = URL(string: "http://localhost:8080/helloworld")! 212 | URLSession.shared.dataTask(with: knownUrl) { data, response, error in 213 | defer { knownRouteExpectation.fulfill() } 214 | // expect to recieve registered response 215 | XCTAssertNil(error) 216 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 217 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Hello world!") 218 | }.resume() 219 | 220 | // when making request to unknown route 221 | let unknownRouteExpectation = self.expectation(description: "") 222 | let unknownUrl = URL(string: "http://localhost:8080")! 223 | URLSession.shared.dataTask(with: unknownUrl) { data, response, error in 224 | defer { unknownRouteExpectation.fulfill() } 225 | // expect to recieve default not found response 226 | XCTAssertNil(error) 227 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 404) 228 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Not Found") 229 | }.resume() 230 | } 231 | } 232 | 233 | func testMultipleRoutes() { 234 | let router = SwiftNIOMock.router(services: [ 235 | Service { 236 | route(GET/.helloworld) { (request, response, next) in 237 | response.sendString(.ok, value: "Hello world!") 238 | next() 239 | } 240 | route(.GET, { $0.head.uri == "/echo" }) { (request, response, next) in 241 | response.sendString(.ok, value: "echo") 242 | next() 243 | } 244 | } 245 | ]) 246 | 247 | withServer(handler: router) { server, expectation in 248 | defer { expectation.fulfill() } 249 | 250 | // when making request to known route 251 | let knownRouteExpectation = self.expectation(description: "") 252 | let knownUrl = URL(string: "http://localhost:8080/helloworld")! 253 | URLSession.shared.dataTask(with: knownUrl) { data, response, error in 254 | defer { knownRouteExpectation.fulfill() } 255 | // expect to recieve registered response 256 | XCTAssertNil(error) 257 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 258 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Hello world!") 259 | }.resume() 260 | 261 | // when making request to another route 262 | let anotherRouteExpectation = self.expectation(description: "") 263 | let anotherUrl = URL(string: "http://localhost:8080/echo")! 264 | URLSession.shared.dataTask(with: anotherUrl) { data, response, error in 265 | defer { anotherRouteExpectation.fulfill() } 266 | // expect to recieve default not found response 267 | XCTAssertNil(error) 268 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 269 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "echo") 270 | }.resume() 271 | 272 | // when making request to unknown route 273 | let unknownRouteExpectation = self.expectation(description: "") 274 | let unknownUrl = URL(string: "http://localhost:8080")! 275 | URLSession.shared.dataTask(with: unknownUrl) { data, response, error in 276 | defer { unknownRouteExpectation.fulfill() } 277 | // expect to recieve default not found response 278 | XCTAssertNil(error) 279 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 404) 280 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Not Found") 281 | }.resume() 282 | } 283 | } 284 | 285 | func testMultipleServices() { 286 | let helloService = Service { 287 | route(GET/.helloworld) { (request, response, next) in 288 | response.sendString(.ok, value: "Hello world!") 289 | next() 290 | } 291 | } 292 | let echoService = Service { 293 | route(GET/.echo) { (request, response, next) in 294 | response.sendString(.ok, value: "echo") 295 | next() 296 | } 297 | } 298 | 299 | let router = SwiftNIOMock.router(services: [ 300 | helloService, 301 | echoService 302 | ]) 303 | 304 | withServer(handler: router) { server, expectation in 305 | defer { expectation.fulfill() } 306 | 307 | // when making request to known route 308 | let knownRouteExpectation = self.expectation(description: "") 309 | let knownUrl = URL(string: "http://localhost:8080/helloworld")! 310 | URLSession.shared.dataTask(with: knownUrl) { data, response, error in 311 | defer { knownRouteExpectation.fulfill() } 312 | // expect to recieve registered response 313 | XCTAssertNil(error) 314 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 315 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Hello world!") 316 | }.resume() 317 | 318 | // when making request to another route 319 | let anotherRouteExpectation = self.expectation(description: "") 320 | let anotherUrl = URL(string: "http://localhost:8080/echo")! 321 | URLSession.shared.dataTask(with: anotherUrl) { data, response, error in 322 | defer { anotherRouteExpectation.fulfill() } 323 | // expect to recieve default not found response 324 | XCTAssertNil(error) 325 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 326 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "echo") 327 | }.resume() 328 | 329 | // when making request to unknown route 330 | let unknownRouteExpectation = self.expectation(description: "") 331 | let unknownUrl = URL(string: "http://localhost:8080")! 332 | URLSession.shared.dataTask(with: unknownUrl) { data, response, error in 333 | defer { unknownRouteExpectation.fulfill() } 334 | // expect to recieve default not found response 335 | XCTAssertNil(error) 336 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 404) 337 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Not Found") 338 | }.resume() 339 | } 340 | } 341 | 342 | func testKeyPaths() { 343 | class HelloService: Service { 344 | let users: [String] 345 | var echo: String 346 | 347 | func user(byName name: String) -> String? { 348 | users.first { $0 == name } 349 | } 350 | 351 | init(users: [String], echo: String) { 352 | self.users = users 353 | self.echo = echo 354 | super.init() 355 | 356 | routes { 357 | GET/.echo == \HelloService.echo 358 | GET/.users == \HelloService.users 359 | GET/.users/.string == HelloService.user(byName:) 360 | } 361 | } 362 | } 363 | 364 | let helloService = HelloService(users: ["Foo", "Bar"], echo: "") 365 | 366 | var router: Middleware! = SwiftNIOMock.router(services: [helloService]) 367 | withServer(handler: router) { server, expectation in 368 | // when making request to known route 369 | let knownUrl = URL(string: "http://localhost:8080/users")! 370 | URLSession.shared.dataTask(with: knownUrl) { data, response, error in 371 | defer { expectation.fulfill() } 372 | // expect to recieve registered response 373 | XCTAssertNil(error) 374 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 375 | let users = try! JSONEncoder().encode(helloService.users) 376 | let usersString = "\(String(data: users, encoding: .utf8)!)" 377 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, usersString) 378 | }.resume() 379 | 380 | helloService.echo = "Hello world!" 381 | 382 | // when making request to another route 383 | let anotherRouteExpectation = self.expectation(description: "") 384 | let anotherUrl = URL(string: "http://localhost:8080/echo")! 385 | URLSession.shared.dataTask(with: anotherUrl) { data, response, error in 386 | defer { anotherRouteExpectation.fulfill() } 387 | // expect to recieve default not found response 388 | XCTAssertNil(error) 389 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 390 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "\"Hello world!\"") 391 | }.resume() 392 | 393 | // when making request to unknown route 394 | let unknownRouteExpectation = self.expectation(description: "") 395 | var request = URLRequest(url: URL(string: "http://localhost:8080/echo")!) 396 | request.httpMethod = "PUT" 397 | 398 | URLSession.shared.dataTask(with: request) { data, response, error in 399 | defer { unknownRouteExpectation.fulfill() } 400 | // expect to recieve default not found response 401 | XCTAssertNil(error) 402 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 404) 403 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "Not Found") 404 | }.resume() 405 | 406 | // when making request to parametrised route 407 | let parameterRouteExpectation = self.expectation(description: "") 408 | let parameterUrl = URL(string: "http://localhost:8080/users/Foo")! 409 | URLSession.shared.dataTask(with: parameterUrl) { data, response, error in 410 | defer { parameterRouteExpectation.fulfill() } 411 | // expect to recieve default not found response 412 | XCTAssertNil(error) 413 | XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200) 414 | XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) }, "\"Foo\"") 415 | }.resume() 416 | } 417 | } 418 | 419 | 420 | } 421 | 422 | -------------------------------------------------------------------------------- /Tests/SwiftNIOMockTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | #if !canImport(ObjectiveC) 2 | import XCTest 3 | 4 | extension SwiftNIOMockTests { 5 | static let __allTests = [ 6 | ("testCanRedirectRequestAndInterceptResponse", testCanRedirectRequestAndInterceptResponse), 7 | ("testCanRestartServer", testCanRestartServer), 8 | ("testCanReturnDefaultResponse", testCanReturnDefaultResponse), 9 | ("testCanRunTwoServersOnDifferentPorts", testCanRunTwoServersOnDifferentPorts), 10 | ] 11 | } 12 | 13 | public func __allTests() -> [XCTestCaseEntry] { 14 | return [ 15 | testCase(SwiftNIOMockTests.__allTests), 16 | ] 17 | } 18 | #endif 19 | --------------------------------------------------------------------------------