Skip to content

Commit

Permalink
Added ability to set cassette request options
Browse files Browse the repository at this point in the history
The options added so far allow a user to select what attributes a request should be matched on.

Request matching options include URL, Path, HTTPMethod and HTTPBody.
  • Loading branch information
pnicholls committed Jul 8, 2016
1 parent 855bef6 commit 1c78f44
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 13 deletions.
12 changes: 12 additions & 0 deletions DVR.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
B19D62721CB1A27700E16D11 /* upload-data.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62701CB1A27700E16D11 /* upload-data.json */; };
B19D62771CB1A42600E16D11 /* upload-file.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62761CB1A42600E16D11 /* upload-file.json */; };
B19D62781CB1A42600E16D11 /* upload-file.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62761CB1A42600E16D11 /* upload-file.json */; };
C191AEF11D2F5B32001EB011 /* CassetteOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEEF1D2F5B32001EB011 /* CassetteOptions.swift */; };
C191AEF21D2F5B32001EB011 /* CassetteOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEEF1D2F5B32001EB011 /* CassetteOptions.swift */; };
C191AEF31D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEF01D2F5B32001EB011 /* RequestMatchingOptions.swift */; };
C191AEF41D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEF01D2F5B32001EB011 /* RequestMatchingOptions.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -92,6 +96,8 @@
B19D626D1CB1A0DD00E16D11 /* SessionUploadTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionUploadTests.swift; sourceTree = "<group>"; };
B19D62701CB1A27700E16D11 /* upload-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "upload-data.json"; sourceTree = "<group>"; };
B19D62761CB1A42600E16D11 /* upload-file.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "upload-file.json"; sourceTree = "<group>"; };
C191AEEF1D2F5B32001EB011 /* CassetteOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CassetteOptions.swift; sourceTree = "<group>"; };
C191AEF01D2F5B32001EB011 /* RequestMatchingOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestMatchingOptions.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -157,6 +163,8 @@
B19D62641CB1860400E16D11 /* SessionUploadTask.swift */,
3647AFB51B335E4A00EF10D4 /* Cassette.swift */,
3647AFB61B335E4A00EF10D4 /* Interaction.swift */,
C191AEEF1D2F5B32001EB011 /* CassetteOptions.swift */,
C191AEF01D2F5B32001EB011 /* RequestMatchingOptions.swift */,
3647AFB71B335E4A00EF10D4 /* URLRequest.swift */,
3647AFBF1B33602A00EF10D4 /* URLResponse.swift */,
3647AFC11B3363C400EF10D4 /* URLHTTPResponse.swift */,
Expand Down Expand Up @@ -394,8 +402,10 @@
3647AFBD1B335E4A00EF10D4 /* Session.swift in Sources */,
360F5F731B5C907A001AADD1 /* SessionDownloadTask.swift in Sources */,
3647AFBC1B335E4A00EF10D4 /* URLRequest.swift in Sources */,
C191AEF31D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */,
3647AFBB1B335E4A00EF10D4 /* Interaction.swift in Sources */,
3647AFBA1B335E4A00EF10D4 /* Cassette.swift in Sources */,
C191AEF11D2F5B32001EB011 /* CassetteOptions.swift in Sources */,
3647AFC21B3363C400EF10D4 /* URLHTTPResponse.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -419,8 +429,10 @@
3690A0981B33AA9400731222 /* URLResponse.swift in Sources */,
360F5F751B5C907A001AADD1 /* SessionDownloadTask.swift in Sources */,
3690A0931B33AA9400731222 /* Session.swift in Sources */,
C191AEF41D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */,
3690A0941B33AA9400731222 /* SessionDataTask.swift in Sources */,
3690A0971B33AA9400731222 /* URLRequest.swift in Sources */,
C191AEF21D2F5B32001EB011 /* CassetteOptions.swift in Sources */,
3690A0991B33AA9400731222 /* URLHTTPResponse.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
129 changes: 122 additions & 7 deletions DVR/Cassette.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,146 @@ struct Cassette {

let name: String
let interactions: [Interaction]

let cassetteOptions: CassetteOptions

// MARK: - Initializers

init(name: String, interactions: [Interaction]) {
init(name: String, interactions: [Interaction], cassetteOptions: CassetteOptions) {
self.name = name
self.interactions = interactions
self.cassetteOptions = cassetteOptions
}


// MARK: - Functions

func interactionForRequest(request: NSURLRequest) -> Interaction? {
for interaction in interactions {
let interactionRequest = interaction.request

if cassetteOptions.requestMatching == [.URL, .Path, .HTTPMethod, .HTTPBody] {
guard
interactionRequest.URL == request.URL &&
interactionRequest.URL?.relativePath == request.URL?.relativePath &&
interactionRequest.HTTPMethod == request.HTTPMethod &&
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.URL, .Path] {
guard
interactionRequest.URL == request.URL &&
interactionRequest.URL?.relativePath == request.URL?.relativePath
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.URL, .HTTPMethod] {
guard
interactionRequest.URL == request.URL &&
interactionRequest.HTTPMethod == request.HTTPMethod
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.URL, .HTTPBody] {
guard
interactionRequest.URL == request.URL &&
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.Path, .HTTPMethod] {
guard
interactionRequest.URL?.relativePath == request.URL?.relativePath &&
interactionRequest.HTTPMethod == request.HTTPMethod
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.Path, .HTTPBody] {
guard
interactionRequest.URL?.relativePath == request.URL?.relativePath &&
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
else {
continue
}

return interaction
}

// Note: We don't check headers right now
if interactionRequest.HTTPMethod == request.HTTPMethod && interactionRequest.URL == request.URL && interactionRequest.hasHTTPBodyEqualToThatOfRequest(request) {
if cassetteOptions.requestMatching == [.HTTPMethod, .HTTPBody] {
guard
interactionRequest.HTTPMethod == request.HTTPMethod &&
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.URL] {
guard
interactionRequest.URL == request.URL
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.Path] {
guard
interactionRequest.URL?.relativePath == request.URL?.relativePath
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.HTTPMethod] {
guard
interactionRequest.HTTPMethod == request.HTTPMethod
else {
continue
}

return interaction
}

if cassetteOptions.requestMatching == [.HTTPBody] {
guard
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
else {
continue
}

return interaction
}
}

return nil
}
}


extension Cassette {
var dictionary: [String: AnyObject] {
return [
Expand All @@ -40,10 +154,11 @@ extension Cassette {
]
}

init?(dictionary: [String: AnyObject]) {
init?(dictionary: [String: AnyObject], cassetteOptions: CassetteOptions) {
guard let name = dictionary["name"] as? String else { return nil }

self.name = name
self.cassetteOptions = cassetteOptions

if let array = dictionary["interactions"] as? [[String: AnyObject]] {
interactions = array.flatMap { Interaction(dictionary: $0) }
Expand Down
20 changes: 20 additions & 0 deletions DVR/CassetteOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// CassetteOptions.swift
// DVR
//
// Created by Peter Nicholls on 6/07/2016.
// Copyright © 2016 Venmo. All rights reserved.
//

public struct CassetteOptions {

// MARK: - Properties

public let requestMatching: RequestMatching

// MARK: - Initializers

public init(requestMatching: RequestMatching = [.URL, .Path, .HTTPMethod, .HTTPBody]) {
self.requestMatching = requestMatching
}
}
33 changes: 33 additions & 0 deletions DVR/RequestMatchingOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// RequestMatching.swift
// DVR
//
// Created by Peter Nicholls on 6/07/2016.
// Copyright © 2016 Venmo. All rights reserved.
//

public struct RequestMatching : OptionSetType {

// MARK: - Properties

private enum Method : Int {
case URL = 1, Path = 2, HTTPMethod = 4, HTTPBody = 8
}

public let rawValue : Int

public static let URL = RequestMatching(Method.URL)
public static let Path = RequestMatching(Method.Path)
public static let HTTPMethod = RequestMatching(Method.HTTPMethod)
public static let HTTPBody = RequestMatching(Method.HTTPBody)

// MARK: - Initializers

public init(rawValue: Int) {
self.rawValue = rawValue
}

private init(_ direction: Method) {
self.rawValue = direction.rawValue
}
}
11 changes: 7 additions & 4 deletions DVR/Session.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public class Session: NSURLSession {
public let cassetteName: String
public let backingSession: NSURLSession
public var recordingEnabled = true

public let cassetteOptions: CassetteOptions

private let testBundle: NSBundle

private var recording = false
Expand All @@ -23,11 +24,13 @@ public class Session: NSURLSession {

// MARK: - Initializers

public init(outputDirectory: String = "~/Desktop/DVR/", cassetteName: String, testBundle: NSBundle = NSBundle.allBundles().filter() { $0.bundlePath.hasSuffix(".xctest") }.first!, backingSession: NSURLSession = NSURLSession.sharedSession()) {
public init(outputDirectory: String = "~/Desktop/DVR/", cassetteName: String, testBundle: NSBundle = NSBundle.allBundles().filter() { $0.bundlePath.hasSuffix(".xctest") }.first!, backingSession: NSURLSession = NSURLSession.sharedSession(), cassetteOptions: CassetteOptions = CassetteOptions()) {
self.outputDirectory = outputDirectory
self.cassetteName = cassetteName
self.testBundle = testBundle
self.backingSession = backingSession
self.cassetteOptions = cassetteOptions

super.init()
}

Expand Down Expand Up @@ -116,7 +119,7 @@ public class Session: NSURLSession {
json = raw as? [String: AnyObject]
else { return nil }

return Cassette(dictionary: json)
return Cassette(dictionary: json, cassetteOptions: cassetteOptions)
}

func finishTask(task: NSURLSessionTask, interaction: Interaction, playback: Bool) {
Expand Down Expand Up @@ -195,7 +198,7 @@ public class Session: NSURLSession {
}
}

let cassette = Cassette(name: cassetteName, interactions: interactions)
let cassette = Cassette(name: cassetteName, interactions: interactions, cassetteOptions: cassetteOptions)

// Persist

Expand Down
6 changes: 4 additions & 2 deletions DVR/SessionDataTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class SessionDataTask: NSURLSessionDataTask {

override func resume() {
let cassette = session.cassette

// Find interaction
if let interaction = session.cassette?.interactionForRequest(request) {
self.interaction = interaction
Expand All @@ -47,12 +47,14 @@ class SessionDataTask: NSURLSessionDataTask {
completion(interaction.responseData, interaction.response, nil)
}
}

session.finishTask(self, interaction: interaction, playback: true)
return
}

if cassette != nil {
print("[DVR] Invalid request. The request was not found in the cassette.")
print("[DVR] Request: ", request)
abort()
}

Expand Down
19 changes: 19 additions & 0 deletions DVR/Tests/SessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,25 @@ class SessionTests: XCTestCase {

waitForExpectationsWithTimeout(1, handler: nil)
}

func testDataTaskWithHTTPBodyRequestMatchingCassetteOptions() {
let cassetteOptions = CassetteOptions(requestMatching: [.HTTPBody])
let session = Session(cassetteName: "example", cassetteOptions: cassetteOptions)

session.recordingEnabled = false
let expectation = expectationWithDescription("Network")

session.dataTaskWithRequest(request) { data, response, error in
XCTAssertEqual("hello", String(data: data!, encoding: NSUTF8StringEncoding))

let HTTPResponse = response as! NSHTTPURLResponse
XCTAssertEqual(200, HTTPResponse.statusCode)

expectation.fulfill()
}.resume()

waitForExpectationsWithTimeout(1, handler: nil)
}

func testTaskDelegate() {
class Delegate: NSObject, NSURLSessionTaskDelegate {
Expand Down

0 comments on commit 1c78f44

Please sign in to comment.