Skip to content

Commit

Permalink
Merge pull request #14 from depoon/multiple-response-per-path-route
Browse files Browse the repository at this point in the history
Adding Capability for multiple responses per path route
  • Loading branch information
depoon authored Feb 15, 2020
2 parents a8ee661 + c4a0519 commit 0bb3b99
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 28 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,21 @@ self.localhostServer.get("/3/movie/popular", routeBlock: { request in
})

```

You can queue up mock responses for the same route path. Mock responses will be returned in FIFO order. Used responses will be removed from the queue unless there is only 1 response left in the queue.

```swift

var dataResponse1, dataResponse2, dataResponse3: Data!

self.localhostServer.route(method: .GET,
path: "/3/movie/popular",
responseData: dataResponse1)
self.localhostServer.route(method: .GET,
path: "/3/movie/popular",
responseData: dataResponse2)
self.localhostServer.route(method: .GET,
path: "/3/movie/popular",
responseData: dataResponse2)
```

2 changes: 1 addition & 1 deletion Source/Localhost/LocalhostRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public protocol LocalhostRouter {
func delete(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?))
func put(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?))
func patch(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?))
func options(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?))
func head(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?))
func options(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?))

func startListening()
func stopListening()
Expand Down
146 changes: 120 additions & 26 deletions Source/Localhost/LocalhostServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,103 @@ import Criollo
extension LocalhostServer: LocalhostRouter {

public func get(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {
let method: String = "GET"
self.setOverlayingRoute(method: method, path: path, routeBlock: routeBlock)
self.server.get(path, block: { [weak self] (req, res, next) in
self?.handleRoute(httpMethod: "GET",
routeBlock: routeBlock,
guard let routeBlockPopped = self?.popOverlayingRoute(method: method, path: path) else {
return
}
self?.handleRoute(httpMethod: method,
routeBlock: routeBlockPopped,
crRequest: req,
crResponse: res)
})
}

public func post(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {
let method: String = "POST"
self.setOverlayingRoute(method: method, path: path, routeBlock: routeBlock)
self.server.post(path, block: { [weak self] (req, res, next) in
self?.handleRoute(httpMethod: "POST",
routeBlock: routeBlock,
guard let routeBlockPopped = self?.popOverlayingRoute(method: method, path: path) else {
return
}
self?.handleRoute(httpMethod: method,
routeBlock: routeBlockPopped,
crRequest: req,
crResponse: res)
})
}

public func delete(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {
let method: String = "DELETE"
self.setOverlayingRoute(method: method, path: path, routeBlock: routeBlock)
self.server.delete(path, block: { [weak self] (req, res, next) in
self?.handleRoute(httpMethod: "DELETE",
routeBlock: routeBlock,
guard let routeBlockPopped = self?.popOverlayingRoute(method: method, path: path) else {
return
}
self?.handleRoute(httpMethod: method,
routeBlock: routeBlockPopped,
crRequest: req,
crResponse: res)
})
}

public func put(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {
let method: String = "PUT"
self.setOverlayingRoute(method: method, path: path, routeBlock: routeBlock)
self.server.put(path, block: { [weak self] (req, res, next) in
self?.handleRoute(httpMethod: "PUT",
routeBlock: routeBlock,
guard let routeBlockPopped = self?.popOverlayingRoute(method: method, path: path) else {
return
}
self?.handleRoute(httpMethod: method,
routeBlock: routeBlockPopped,
crRequest: req,
crResponse: res)
})
}

public func patch(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {
let method: String = "PATCH"
self.setOverlayingRoute(method: method, path: path, routeBlock: routeBlock)
self.server.add(path, block: { [weak self] (req, res, next) in
self?.handleRoute(httpMethod: "PATCH",
routeBlock: routeBlock,
guard let routeBlockPopped = self?.popOverlayingRoute(method: method, path: path) else {
return
}
self?.handleRoute(httpMethod: method,
routeBlock: routeBlockPopped,
crRequest: req,
crResponse: res)
}, recursive: false, method: .patch)
})
}

public func head(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {
let method: String = "HEAD"
self.setOverlayingRoute(method: method, path: path, routeBlock: routeBlock)
self.server.head(path, block: { [weak self] (req, res, next) in
self?.handleRoute(httpMethod: "HEAD",
routeBlock: routeBlock,
guard let routeBlockPopped = self?.popOverlayingRoute(method: method, path: path) else {
return
}
self?.handleRoute(httpMethod: method,
routeBlock: routeBlockPopped,
crRequest: req,
crResponse: res)
})
}

public func options(_ path: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {
let method: String = "OPTIONS"
self.setOverlayingRoute(method: method, path: path, routeBlock: routeBlock)
self.server.options(path, block: { [weak self] (req, res, next) in
self?.handleRoute(httpMethod: "OPTIONS",
routeBlock: routeBlock,
guard let routeBlockPopped = self?.popOverlayingRoute(method: method, path: path) else {
return
}
self?.handleRoute(httpMethod: method,
routeBlock: routeBlockPopped,
crRequest: req,
crResponse: res)
})
}

public func startListening(){
self.server.startListening(nil, portNumber: self.portNumber)
}
Expand All @@ -88,12 +123,15 @@ public class LocalhostServer {
public let portNumber: UInt
let server: CRHTTPServer

var overlayingRoutes: [LocalhostServerMethodPath: [LocalhostServerRoute]]

public var recordedRequests: [URLRequest]

public required init(portNumber: UInt){
server = CRHTTPServer()
self.portNumber = portNumber
recordedRequests = [URLRequest]()
overlayingRoutes = [LocalhostServerMethodPath: [LocalhostServerRoute]]()
}

public static func initializeUsingRandomPortNumber() -> LocalhostServer{
Expand All @@ -102,8 +140,8 @@ public class LocalhostServer {
}

fileprivate func handleRoute(httpMethod: String, routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?),
crRequest: CRRequest,
crResponse: CRResponse) {
crRequest: CRRequest,
crResponse: CRResponse) {
var request = URLRequest(url: crRequest.url)
request.httpMethod = httpMethod
request.allHTTPHeaderFields = crRequest.allHTTPHeaderFields
Expand All @@ -130,10 +168,10 @@ public class LocalhostServer {
}

public func route(method: LocalhostRequestMethod,
path: String,
responseData: Data,
statusCode: Int = 200,
responseHeaderFields: [String: String]? = nil) {
path: String,
responseData: Data,
statusCode: Int = 200,
responseHeaderFields: [String: String]? = nil) {
let routeBlock = self.routeBlock(path: path,
responseData: responseData,
statusCode: statusCode,
Expand Down Expand Up @@ -182,7 +220,7 @@ public struct LocalhostRequest {
public init(method: LocalhostRequestMethod, url: URL) {
self.method = method
self.url = url
}
}
}

protocol LocalhostResponse {
Expand Down Expand Up @@ -222,3 +260,59 @@ extension CRRequest {
}
}

struct LocalhostServerMethodPath: Hashable, Equatable {
let method: String
let path: String

func hash(into hasher: inout Hasher) {
hasher.combine(method)
hasher.combine(path)
}

public static func ==(lhs: LocalhostServerMethodPath, rhs: LocalhostServerMethodPath) -> Bool {
guard lhs.method == rhs.method else { return false }
guard lhs.path == rhs.path else { return false }
return true
}
}

struct LocalhostServerRoute {
let pathMethod: LocalhostServerMethodPath
let routeBlock: ((URLRequest) -> LocalhostServerResponse?)
}

extension LocalhostServer {
func setOverlayingRoute(method: String,
path: String,
routeBlock: @escaping ((URLRequest) -> LocalhostServerResponse?)) {

let localhostServerMethodPath = LocalhostServerMethodPath(method: method, path: path)
let newServerRoute = LocalhostServerRoute(pathMethod: localhostServerMethodPath, routeBlock: routeBlock)
let methodPathKeys = Array(self.overlayingRoutes.keys)
if methodPathKeys.contains(localhostServerMethodPath), var existingRoutes = self.overlayingRoutes[localhostServerMethodPath] {
existingRoutes.append(newServerRoute)
self.overlayingRoutes[localhostServerMethodPath] = existingRoutes
} else {
let routes: [LocalhostServerRoute] = [
newServerRoute
]
self.overlayingRoutes[localhostServerMethodPath] = routes
}
}

func popOverlayingRoute(method: String, path: String) -> ((URLRequest) -> LocalhostServerResponse?)? {
let localhostServerMethodPath = LocalhostServerMethodPath(method: method, path: path)
let methodPathKeys = Array(self.overlayingRoutes.keys)
guard methodPathKeys.contains(localhostServerMethodPath),
var existingRoutes = self.overlayingRoutes[localhostServerMethodPath],
existingRoutes.count > 0 else {
return nil
}
if existingRoutes.count == 1 {
return existingRoutes.first?.routeBlock
}
let firstItem = existingRoutes.removeFirst()
self.overlayingRoutes[localhostServerMethodPath] = existingRoutes
return firstItem.routeBlock
}
}
2 changes: 1 addition & 1 deletion SwiftLocalhost.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Pod::Spec.new do |s|
s.name = "SwiftLocalhost"
s.version = "0.0.7"
s.version = "0.0.8"
s.swift_version = '4.2'
s.summary = "Swift Localhost Server for your testing needs"
s.description = <<-DESC
Expand Down

0 comments on commit 0bb3b99

Please sign in to comment.