diff --git a/.gitignore b/.gitignore index e4f83d9..9042a29 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,10 @@ xcuserdata timeline.xctimeline playground.xcworkspace +# AppCode +.idea +.idea/ + # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. @@ -56,7 +60,7 @@ Carthage/Build # fastlane # -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md diff --git a/Source/JSONAPIError.swift b/Source/JSONAPIError.swift index b45e3e4..501577f 100644 --- a/Source/JSONAPIError.swift +++ b/Source/JSONAPIError.swift @@ -8,7 +8,7 @@ import Foundation -/// A convenince error object that conform to JSON API +/// A convenience error object that conform to JSON API public struct JSONAPIError: ResponseFieldsProvider { /// An object containing references to the source of the error, optionally including any of the following members diff --git a/Source/JSONAPILinks.swift b/Source/JSONAPILinks.swift index e123179..2880488 100644 --- a/Source/JSONAPILinks.swift +++ b/Source/JSONAPILinks.swift @@ -82,7 +82,7 @@ public enum JSONAPILink: CustomSerializable { } ``` - Appart from their own entity links, and in order to provide extra links for relationships, `User` must specify them for each relationship key: + Apart from their own entity links, and in order to provide extra links for relationships, `User` must specify them for each relationship key: ```swift let cats = [Cat(id: "33", name: "Stancho", links: nil), @@ -111,7 +111,7 @@ public enum JSONAPILink: CustomSerializable { public protocol JSONAPILinkedEntity { /// The related links, must use link-names as keys and links as values. var links: [String : JSONAPILink]? { get } - /// The relationships links, an object containing relationsips can specify top level links for every relationship type. The object must provide a Dictionary where keys are the relationships types and values are dictionaries with link-names as keys and link as values. + /// The relationships links, an object containing relationships can specify top level links for every relationship type. The object must provide a Dictionary where keys are the relationships types and values are dictionaries with link-names as keys and link as values. var relationshipsLinks: [String : [String : JSONAPILink]]? { get } } diff --git a/Source/JSONAPISerializer.swift b/Source/JSONAPISerializer.swift index 75bc53d..2f23343 100644 --- a/Source/JSONAPISerializer.swift +++ b/Source/JSONAPISerializer.swift @@ -257,7 +257,7 @@ public extension JSONAPIEntity { // MARK: JSONAPIEntity - /// returns the lowercased class name as string by default + /// returns the lower-cased class name as string by default static var type: String { return String(self).lowercaseString } @@ -332,7 +332,7 @@ public extension JSONAPIEntity { return data } - /// returns the `included` relationsips field conforming to JSON API + /// returns the `included` relationships field conforming to JSON API public func includedRelationships(includeChildren: Bool, keyTransformer: KeyTransformer?) -> [AnyObject]? { let mirror = Mirror(reflecting: self) let includedRelationships = mirror.children.flatMap { (label, value) -> [AnyObject] in diff --git a/Source/KakapoDB.swift b/Source/KakapoDB.swift index 9194eda..27de5d0 100644 --- a/Source/KakapoDB.swift +++ b/Source/KakapoDB.swift @@ -19,7 +19,7 @@ public protocol Storable { An initializer that is used by `KakapoDB` to create objects to be stored in the db - parameter id: The unique identifier provided by `KakapoDB`, objects shouldn't generate ids themselves. `KakapoDB` generate `Int` ids converted to String for better compatibilities with standards like JSONAPI, in case you need `Int` ids is safe to ssume that the conversion will always succeeed. - - parameter db: The db that is creating the object, can be used to generate other `Storable` objects, for example relationsips of the object: `myRelationship = db.create(MyRelationshipType)` or `myrelationship = db.insert { MyRelationshipType(id: $0, db: db) }`. The relationsip will also recieve the `db` instance to eventually initialize its relationships. + - parameter db: The db that is creating the object, can be used to generate other `Storable` objects, for example relationships of the object: `myRelationship = db.create(MyRelationshipType)` or `myrelationship = db.insert { MyRelationshipType(id: $0, db: db) }`. The relationsip will also recieve the `db` instance to eventually initialize its relationships. - returns: A configured object stored in the db. */ @@ -93,7 +93,7 @@ public final class KakapoDB { /** Creates and inserts Storable objects based on their default initializer - - parameter (unamed): The Storable Type to be created + - parameter (unnamed): The Storable Type to be created - parameter number: The number of elements to create, defaults to 1 - returns: An array containing the new inserted Storable objects @@ -181,7 +181,7 @@ public final class KakapoDB { /** Find all the objects in the store of a given Storable Type - - parameter (unamed): The Storable Type to be found + - parameter (unnamed): The Storable Type to be found - returns: An array containing the found Storable objects */ @@ -194,7 +194,7 @@ public final class KakapoDB { /** Filter all the objects in the store of a given Storable Type that satisfy the a given handler - - parameter (unamed): The Storable Type to be filtered + - parameter (unnamed): The Storable Type to be filtered - parameter includeElement: The predicate to satisfy the filtering - returns: An array containing the filtered Storable objects @@ -206,10 +206,10 @@ public final class KakapoDB { /** Find the object in the store by a given id - - parameter (unamed): The Storable Type to be filtered + - parameter (unnamed): The Storable Type to be filtered - parameter id: The id to search for - - returns: An optional thay may (or not) contain the found Storable object + - returns: An optional, which may (or may not) contain the found Storable object */ public func find(_: T.Type, id: String) -> T? { return filter(T.self) { $0.id == id }.first diff --git a/Source/KakapoServer.swift b/Source/KakapoServer.swift index af43d5b..933bc2f 100644 --- a/Source/KakapoServer.swift +++ b/Source/KakapoServer.swift @@ -9,8 +9,10 @@ import Foundation /** - A server that conforms to NSURLProtocol in order to intercept outgoing network communication. - You shouldn't use this class directly but register a `Router` instead. Since frameworks like **AFNetworking** and **Alamofire** require manual registration of the `NSURLProtocol` classes you will need to register this class when needed. + A server that conforms to `NSURLProtocol` in order to intercept outgoing network communication. + You shouldn't use this class directly but register a `Router` instead. + Since frameworks like **AFNetworking** and **Alamofire** require manual registration of the `NSURLProtocol` classes + you will need to register this class when needed. ### Examples @@ -37,15 +39,24 @@ import Foundation ``` */ public final class KakapoServer: NSURLProtocol { - + private static var routers: [Router] = [] + + /** + `true`, if the `request` of the `KakapoServer` instance has been cancelled, otherwise `false`. + + Default: `false` + + Note: calls to `stopLoading()` will set this value to `true` + */ + private(set) var requestCancelled:Bool = false /** Register and return a new Router in the Server - parameter baseURL: The base URL that this Router will use - - returns: An new initializcaRouter objects can hold the same baseURL. + - returns: A newly initialized Router object, which is configured to use the `baseURL`. */ class func register(baseURL: String) -> Router { NSURLProtocol.registerClass(self) @@ -74,14 +85,19 @@ public final class KakapoServer: NSURLProtocol { } /** - KakapoServer checks if the given request matches any of the registered routes and determines if the request should be intercepted + `KakapoServer` checks if the given request matches any of the registered routes + and determines if the request should be intercepted. + + Note: If this method returns `true`, then the OS will create a new `KakapoServer` instance for the `request` + via the `init(request:cachedResponse:client:)` initializer. So, this `request` is the same, which + we'll have access to later on in the `startLoading()` and `stopLoading()` methods. - parameter request: A request - returns: true if any of the registered route match the request URL */ override public class func canInitWithRequest(request: NSURLRequest) -> Bool { - return routers.filter { $0.canInitWithRequest(request) }.first != nil + return routers.indexOf({ $0.canInitWithRequest(request) }) != nil } /// Just returns the given request without changes @@ -91,11 +107,17 @@ public final class KakapoServer: NSURLProtocol { /// Start loading the matched requested, the route handler will be called and the returned object will be serialized. override public func startLoading() { - KakapoServer.routers.filter { $0.canInitWithRequest(request) }.first!.startLoading(self) + if requestCancelled { + return + } + + if let routerIndex = KakapoServer.routers.indexOf({ $0.canInitWithRequest(request) }) { + KakapoServer.routers[routerIndex].startLoading(self) + } } - /// Not implemented yet, does nothing ATM. https://github.com/devlucky/Kakapo/issues/88 + /// Stops the loading of the matched request. override public func stopLoading() { - /* TODO: implement stopLoading for delayed requests https://github.com/devlucky/Kakapo/issues/88 */ + requestCancelled = true } } diff --git a/Source/RouteMatcher.swift b/Source/RouteMatcher.swift index 97373a8..b5e965e 100644 --- a/Source/RouteMatcher.swift +++ b/Source/RouteMatcher.swift @@ -14,7 +14,7 @@ import Foundation public typealias URLInfo = (components: [String : String], queryParameters: [NSURLQueryItem]) /** - Match a route and a requestURL. A route is composed by a baseURL and a path, togheter they should match the given requestURL. + Match a route and a requestURL. A route is composed by a baseURL and a path, together they should match the given requestURL. To match a route the baseURL must be contained in the requestURL, the substring of the requestURL following the baseURL then is tested against the path to check if they match. A baseURL can contain a scheme, and the requestURL must match the scheme; if it doesn't contain a scheme then the baseURL is a wildcard and will be matched by any subdomain or any scheme: @@ -23,7 +23,7 @@ public typealias URLInfo = (components: [String : String], queryParameters: [NSU - base: `kakapo.com`, path: "any", requestURL: "https://kakapo.com/any" ✅ - base: `kakapo.com`, path: "any", requestURL: "https://api.kakapo.com/any" ✅ - A path can contain wildcard components prefixed with ":" (e.g. /users/:userid) that are used to build the component dictionary, the wildcard is then used as key and the repsective component of the requestURL is used as value. + A path can contain wildcard components prefixed with ":" (e.g. /users/:userid) that are used to build the component dictionary, the wildcard is then used as key and the respective component of the requestURL is used as value. Any component that is not a wildcard have to be exactly the same in both the path and the request, otherwise the route won't match. - `/users/:userid` and `/users/1234` ✅ -> `[userid: 1234]` @@ -35,7 +35,7 @@ public typealias URLInfo = (components: [String : String], queryParameters: [NSU - parameter path: The path of the request, can contain wildcards components prefixed with ":" (e.g. /users/:id/) - parameter requestURL: The URL of the request (e.g. https://kakapo.com/api/users/1234) - - returns: A URL info object containing `components` and `queryParamaters` or nil if `requestURL`doesn't match the route. + - returns: A URL info object containing `components` and `queryParameters` or nil if `requestURL`doesn't match the route. */ func matchRoute(baseURL: String, path: String, requestURL: NSURL) -> URLInfo? { @@ -91,7 +91,7 @@ private extension String { } /** - Retrun the substring From/To a given string or nil if the string is not contained. + Return the substring From/To a given string or nil if the string is not contained. - **From**: return the substring following the given string (e.g. `kakapo.com/users`, `kakapo.com` -> `/users`) - **To**: return the substring preceding the given string (e.g. `kakapo.com/users?a=b`, `?` -> `kakapo.com/users`) */ diff --git a/Source/Router.swift b/Source/Router.swift index b58a71f..26866a1 100644 --- a/Source/Router.swift +++ b/Source/Router.swift @@ -35,7 +35,8 @@ public struct Request { /** A protocol to adopt when a `Serializable object needs to also provide response status code and/or headerFields - For example you may use `Response` to wrap your `Serializable` object to just achieve the result or directly implement the protocol. For examply `JSONAPISerializer` implement the protocol in order to be able to provide custom status code in the response. + For example you may use `Response` to wrap your `Serializable` object to just achieve the result or directly implement the protocol. + For example `JSONAPISerializer` implement the protocol in order to be able to provide custom status code in the response. */ public protocol ResponseFieldsProvider: CustomSerializable { /// The response status code @@ -59,7 +60,7 @@ extension ResponseFieldsProvider { /** A ResponseFieldsProvider implementation which can be used in `RouteHandlers` to provide valid responses that can return different status code than the default (200) or headerFields. - The struct provides, appart from a Serializable `body` object, a status code and header fields. + The struct provides, apart from a Serializable `body` object, a status code and header fields. */ public struct Response: ResponseFieldsProvider { /// The response status code @@ -72,11 +73,11 @@ public struct Response: ResponseFieldsProvider { public let headerFields: [String : String]? /** - Initialize `Response` object that wraps another `Serializable` object for the serialization but, implemententing `ResponseFieldsProvider` can affect some parameters of the HTTP response + Initialize `Response` object that wraps another `Serializable` object for the serialization but, implementing `ResponseFieldsProvider` can affect some parameters of the HTTP response - - parameter statusCode: the status code that the response should provide to the HTTP repsonse + - parameter statusCode: the status code that the response should provide to the HTTP response - parameter body: the body that will be serialized - - parameter headerFields: the headerFields that the response should provide to the HTTP repsonse + - parameter headerFields: the headerFields that the response should provide to the HTTP response - returns: A wrapper `Serializable` object that affect http requests. */ @@ -101,17 +102,17 @@ public final class Router { } private var routes: [String : Route] = [:] - + /// The `baseURL` of the Router public let baseURL: String - /// The desired latency to delay the mocked responses. Default value is 0. + /// The desired latency (in seconds) to delay the mocked responses. Default value is 0. public var latency: NSTimeInterval = 0 - - + /** - Register a new Router in the KakapoServer. - The baseURL can contain a scheme, and the requestURL must match the scheme; if it doesn't contain a scheme then the baseURL is a wildcard and will be matched by any subdomain or any scheme: + Register a new Router in the `KakapoServer`. + The `baseURL` can contain a scheme, and the requestURL must match the scheme; if it doesn't contain a scheme then + the `baseURL` is a wildcard and will be matched by any subdomain or any scheme: - base: `http://kakapo.com`, path: "any", requestURL: "http://kakapo.com/any" ✅ - base: `http://kakapo.com`, path: "any", requestURL: "https://kakapo.com/any" ❌ because it's **https** @@ -172,7 +173,7 @@ public final class Router { return false } - func startLoading(server: NSURLProtocol) { + func startLoading(server: KakapoServer) { guard let requestURL = server.request.URL, client = server.client else { return } @@ -185,8 +186,9 @@ public final class Router { // If the request body is nil use `NSURLProtocol` property see swizzling in `NSMutableURLRequest.m` // using a literal string because a bridging header in the podspec will be more problematic. let dataBody = server.request.HTTPBody ?? NSURLProtocol.propertyForKey("kkp_requestHTTPBody", inRequest: server.request) as? NSData - - serializableObject = route.handler(Request(components: info.components, queryParameters: info.queryParameters, HTTPBody: dataBody, HTTPHeaders: server.request.allHTTPHeaderFields)) + + let request = Request(components: info.components, queryParameters: info.queryParameters, HTTPBody: dataBody, HTTPHeaders: server.request.allHTTPHeaderFields) + serializableObject = route.handler(request) break } } @@ -210,14 +212,17 @@ public final class Router { let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(latency * Double(NSEC_PER_SEC))) dispatch_after(delayTime, dispatch_get_main_queue()) { - didFinishLoading(server) + // before reporting "finished", check if request has been canceled in the meantime + if server.requestCancelled == false { + didFinishLoading(server) + } } } - + /** Registers a GET request with the given path. - The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: + The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: - "/users/:userid" and "/users/1234" will produce [userid: 1234] @@ -239,7 +244,7 @@ public final class Router { /** Registers a POST request with the given path - The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: + The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: - "/users/:userid" and "/users/1234" will produce [userid: 1234] @@ -261,7 +266,7 @@ public final class Router { /** Registers a DEL request with the given path - The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: + The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: - "/users/:userid" and "/users/1234" will produce [userid: 1234] @@ -283,7 +288,7 @@ public final class Router { /** Registers a PUT request with the given path - The path is used togheter with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retreive the components of the request: + The path is used together with the `Router.baseURL` to match requests. It can contain wildcard components prefixed by ":" that are later used to retrieve the components of the request: - "/users/:userid" and "/users/1234" will produce [userid: 1234] @@ -301,5 +306,5 @@ public final class Router { public func put(path: String, handler: RouteHandler) { routes[path] = (.PUT, handler) } - + } diff --git a/Source/SerializationTransformer.swift b/Source/SerializationTransformer.swift index a4d8e2a..c35f538 100644 --- a/Source/SerializationTransformer.swift +++ b/Source/SerializationTransformer.swift @@ -73,7 +73,7 @@ public struct SnakecaseTransformer: SerializationTransfor - parameter wrapped: A wrapped `Serializable` object - - returns: A `Serializable` object that will trasform the keys of the wrapped object when serialzied. + - returns: A `Serializable` object that will transform the keys of the wrapped object when serialized. */ public init(_ wrapped: Wrapped) { self.wrapped = wrapped @@ -102,10 +102,10 @@ private extension String { for (idx, c) in charactersView.reverse().enumerate() { let char = String(c) - let lowercased = char.lowercaseString - let isUppercase = char != lowercased + let lowerCased = char.lowercaseString + let isUppercase = char != lowerCased - string.insert(Character(lowercased), atIndex: startIndex) + string.insert(Character(lowerCased), atIndex: startIndex) if isUppercase && idx != endIndex { string.insert("_", atIndex: startIndex) diff --git a/Source/Serializer.swift b/Source/Serializer.swift index 8d3a843..338dd93 100644 --- a/Source/Serializer.swift +++ b/Source/Serializer.swift @@ -45,7 +45,7 @@ public extension Serializable { } /** - Serialize a `Serializable` object and convert the serialzied object to `Data`. Unless it is nil the return value is representing a JSON. Usually you don't need to use this method directly since `Router` will automatically serialize objects when needed. + Serialize a `Serializable` object and convert the serialized object to `Data`. Unless it is nil the return value is representing a JSON. Usually you don't need to use this method directly since `Router` will automatically serialize objects when needed. - returns: The serialized object as `Data` */ @@ -126,7 +126,7 @@ private func serializeObject(value: Any, keyTransformer: KeyTransformer?) -> Any - parameter object: A Serializable object, not a `CustomSerializable` - parameter keyTransformer: The keyTransformer to be used, if not nil, to transform the keys of the json - - returns: A serialized object that may be convered to JSON, usually Array or Dictionary + - returns: A serialized object that may be converted to JSON, usually Array or Dictionary */ private func serialize(object: Serializable, keyTransformer: KeyTransformer?) -> AnyObject { assert(!(object is CustomSerializable)) diff --git a/Tests/RouterTests.swift b/Tests/RouterTests.swift index 7291e3a..40cb10d 100644 --- a/Tests/RouterTests.swift +++ b/Tests/RouterTests.swift @@ -23,7 +23,7 @@ import Alamofire Thus, we use this test server to intercept real network calls in tests as a fallback for `KakapoServer`. */ private final class RouterTestServer: NSURLProtocol { - + class func register() { NSURLProtocol.registerClass(self) } @@ -43,7 +43,7 @@ private final class RouterTestServer: NSURLProtocol { override func startLoading() { guard let requestURL = request.URL, client = client else { return } - + client.URLProtocol(self, didReceiveResponse: NSHTTPURLResponse(URL: requestURL, statusCode: 200, HTTPVersion: "HTTP/1.1", headerFields: nil)!, cacheStoragePolicy: .AllowedInMemoryOnly) client.URLProtocolDidFinishLoading(self) } @@ -51,6 +51,17 @@ private final class RouterTestServer: NSURLProtocol { override func stopLoading() {} } +private final class ProtocolClientTest: NSObject, NSURLProtocolClient { + @objc func URLProtocol(`protocol`: NSURLProtocol, wasRedirectedToRequest request: NSURLRequest, redirectResponse: NSURLResponse) { /* intentionally left empty */ } + @objc func URLProtocol(`protocol`: NSURLProtocol, cachedResponseIsValid cachedResponse: NSCachedURLResponse) { /* intentionally left empty */ } + @objc func URLProtocol(`protocol`: NSURLProtocol, didReceiveResponse response: NSURLResponse, cacheStoragePolicy policy: NSURLCacheStoragePolicy) { /* intentionally left empty */ } + @objc func URLProtocol(`protocol`: NSURLProtocol, didLoadData data: NSData) { /* intentionally left empty */ } + @objc func URLProtocolDidFinishLoading(`protocol`: NSURLProtocol) { /* intentionally left empty */ } + @objc func URLProtocol(`protocol`: NSURLProtocol, didFailWithError error: NSError) { /* intentionally left empty */ } + @objc func URLProtocol(`protocol`: NSURLProtocol, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge) { /* intentionally left empty */ } + @objc func URLProtocol(`protocol`: NSURLProtocol, didCancelAuthenticationChallenge challenge: NSURLAuthenticationChallenge) { /* intentionally left empty */ } +} + struct CustomResponse: ResponseFieldsProvider { let statusCode: Int let body: Serializable @@ -77,7 +88,96 @@ class RouterTests: QuickSpec { RouterTestServer.disable() Router.disableAll() } - + + describe("Cancelling requests") { + var router: Router! + let baseURL = "http://www.funky-cancel-request.com" + let latency = NSTimeInterval(2) + + beforeEach { + router = Router.register(baseURL) + router.latency = latency // very high latency to allow us to stop the request before execution + } + + it("should mark a request as cancelled") { + var responseError: NSError? = nil + + router.get("/foobar/:id") { request in + XCTFail("Request should get cancelled before execution") + return nil + } + + let requestURL = NSURL(string: "\(baseURL)/foobar/1")! + + let dataTask = NSURLSession.sharedSession().dataTaskWithURL(requestURL) { (data, response, error) in + responseError = error + } + + dataTask.cancel() + + expect(responseError).toEventually(beTruthy(), timeout: (latency + 1)) + expect(responseError?.localizedDescription).toEventually(equal("cancelled"), timeout: (latency + 1)) + } + + it("should not confuse multiple request with identical URL") { + var responseURL_A: NSURL? = nil + var responseError_B: NSError? = nil + let canceledRequestID = "999" + + router.get("/cash/:id") { request in + let paramID = request.components["id"] + if paramID == canceledRequestID { + XCTFail("Cancelled request should not get executed") + } + return nil + } + + let requestURL_A = NSURL(string: "\(baseURL)/cash/333")! + let requestURL_B = NSURL(string: "\(baseURL)/cash/\(canceledRequestID)")! + + let dataTask_A = NSURLSession.sharedSession().dataTaskWithURL(requestURL_A) { (data, response, error) in + responseURL_A = response?.URL + } + let dataTask_B = NSURLSession.sharedSession().dataTaskWithURL(requestURL_B) { (data, response, error) in + responseError_B = error + } + + dataTask_A.resume() + dataTask_B.cancel() // cancel immediately -> should never get executed, because of Router.latency + + // expect task A to succeed + expect(responseURL_A).toEventually(beTruthy(), timeout: (latency + 1)) + + // expect task B to get cancelled + expect(responseError_B).toEventually(beTruthy(), timeout: (latency + 1)) + expect(responseError_B?.localizedDescription).toEventually(equal("cancelled"), timeout: (latency + 1)) + } + + it("should send notifications when loading has finished") { + + router.get("/epic-fail/:id") { request in + XCTFail("Expected that request, which has been marked as 'cancelled', not to be executed") + return nil + } + + let requestURL = NSURL(string: "\(baseURL)/epic-fail/1")! + + /* + Note: we need to manually create a KakapoServer instance here to test the stopping logic, because + usually the KakapoServer instances are automatically created by the operating system. + And there's no way for us to access these automatically created instances from the Router. + Therefore we simulate the "stopLoading" and "startLoading" mechanism manually. + */ + let urlRequest = NSURLRequest(URL: requestURL) + let client = ProtocolClientTest() + let server = KakapoServer(request: urlRequest, cachedResponse: nil, client: client) + + server.stopLoading() + expect(server.requestCancelled).to(beTrue()) + server.startLoading() // this should not trigger the "/epic-fail/:id" router request (see XCTFail above) + } + } + describe("Registering urls") { var router: Router! @@ -529,7 +629,7 @@ class RouterTests: QuickSpec { describe("Multiple Routers") { var router: Router! - it("Should handle multiple Routers that register differents urls") { + it("Should handle multiple Routers that register different URLs") { router = Router.register("http://www.test.com") var info: URLInfo? = nil @@ -634,7 +734,7 @@ class RouterTests: QuickSpec { expect(responseURL?.absoluteString).toEventually(equal("http://www.test.com/users/1/comments/2")) } - it("Should properly handle multiple registered and unregistered Routers that register differents urls") { + it("Should properly handle multiple registered and unregistered Routers that register different URLs") { router = Router.register("http://www.test.com") var info: URLInfo? = nil @@ -768,7 +868,7 @@ class RouterTests: QuickSpec { expect(thirdResponseURL?.host).toEventually(equal("www.another.com")) } - it("should not leak any router when disabling or unregistering") { + it("should not leak any router when router gets disabled or unregistered") { weak var router1: Router? = Router.register("www.host1.com") weak var router2: Router? = Router.register("www.host2.com") weak var router3: Router? = Router.register("www.host3.com")