Skip to content

Commit 1c78f44

Browse files
committed
Added ability to set cassette request options
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.
1 parent 855bef6 commit 1c78f44

7 files changed

Lines changed: 217 additions & 13 deletions

File tree

DVR.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
B19D62721CB1A27700E16D11 /* upload-data.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62701CB1A27700E16D11 /* upload-data.json */; };
4848
B19D62771CB1A42600E16D11 /* upload-file.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62761CB1A42600E16D11 /* upload-file.json */; };
4949
B19D62781CB1A42600E16D11 /* upload-file.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62761CB1A42600E16D11 /* upload-file.json */; };
50+
C191AEF11D2F5B32001EB011 /* CassetteOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEEF1D2F5B32001EB011 /* CassetteOptions.swift */; };
51+
C191AEF21D2F5B32001EB011 /* CassetteOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEEF1D2F5B32001EB011 /* CassetteOptions.swift */; };
52+
C191AEF31D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEF01D2F5B32001EB011 /* RequestMatchingOptions.swift */; };
53+
C191AEF41D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C191AEF01D2F5B32001EB011 /* RequestMatchingOptions.swift */; };
5054
/* End PBXBuildFile section */
5155

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

97103
/* Begin PBXFrameworksBuildPhase section */
@@ -157,6 +163,8 @@
157163
B19D62641CB1860400E16D11 /* SessionUploadTask.swift */,
158164
3647AFB51B335E4A00EF10D4 /* Cassette.swift */,
159165
3647AFB61B335E4A00EF10D4 /* Interaction.swift */,
166+
C191AEEF1D2F5B32001EB011 /* CassetteOptions.swift */,
167+
C191AEF01D2F5B32001EB011 /* RequestMatchingOptions.swift */,
160168
3647AFB71B335E4A00EF10D4 /* URLRequest.swift */,
161169
3647AFBF1B33602A00EF10D4 /* URLResponse.swift */,
162170
3647AFC11B3363C400EF10D4 /* URLHTTPResponse.swift */,
@@ -394,8 +402,10 @@
394402
3647AFBD1B335E4A00EF10D4 /* Session.swift in Sources */,
395403
360F5F731B5C907A001AADD1 /* SessionDownloadTask.swift in Sources */,
396404
3647AFBC1B335E4A00EF10D4 /* URLRequest.swift in Sources */,
405+
C191AEF31D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */,
397406
3647AFBB1B335E4A00EF10D4 /* Interaction.swift in Sources */,
398407
3647AFBA1B335E4A00EF10D4 /* Cassette.swift in Sources */,
408+
C191AEF11D2F5B32001EB011 /* CassetteOptions.swift in Sources */,
399409
3647AFC21B3363C400EF10D4 /* URLHTTPResponse.swift in Sources */,
400410
);
401411
runOnlyForDeploymentPostprocessing = 0;
@@ -419,8 +429,10 @@
419429
3690A0981B33AA9400731222 /* URLResponse.swift in Sources */,
420430
360F5F751B5C907A001AADD1 /* SessionDownloadTask.swift in Sources */,
421431
3690A0931B33AA9400731222 /* Session.swift in Sources */,
432+
C191AEF41D2F5B32001EB011 /* RequestMatchingOptions.swift in Sources */,
422433
3690A0941B33AA9400731222 /* SessionDataTask.swift in Sources */,
423434
3690A0971B33AA9400731222 /* URLRequest.swift in Sources */,
435+
C191AEF21D2F5B32001EB011 /* CassetteOptions.swift in Sources */,
424436
3690A0991B33AA9400731222 /* URLHTTPResponse.swift in Sources */,
425437
);
426438
runOnlyForDeploymentPostprocessing = 0;

DVR/Cassette.swift

Lines changed: 122 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,146 @@ struct Cassette {
66

77
let name: String
88
let interactions: [Interaction]
9-
9+
let cassetteOptions: CassetteOptions
1010

1111
// MARK: - Initializers
1212

13-
init(name: String, interactions: [Interaction]) {
13+
init(name: String, interactions: [Interaction], cassetteOptions: CassetteOptions) {
1414
self.name = name
1515
self.interactions = interactions
16+
self.cassetteOptions = cassetteOptions
1617
}
1718

18-
1919
// MARK: - Functions
2020

2121
func interactionForRequest(request: NSURLRequest) -> Interaction? {
2222
for interaction in interactions {
2323
let interactionRequest = interaction.request
24+
25+
if cassetteOptions.requestMatching == [.URL, .Path, .HTTPMethod, .HTTPBody] {
26+
guard
27+
interactionRequest.URL == request.URL &&
28+
interactionRequest.URL?.relativePath == request.URL?.relativePath &&
29+
interactionRequest.HTTPMethod == request.HTTPMethod &&
30+
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
31+
else {
32+
continue
33+
}
34+
35+
return interaction
36+
}
37+
38+
if cassetteOptions.requestMatching == [.URL, .Path] {
39+
guard
40+
interactionRequest.URL == request.URL &&
41+
interactionRequest.URL?.relativePath == request.URL?.relativePath
42+
else {
43+
continue
44+
}
45+
46+
return interaction
47+
}
48+
49+
if cassetteOptions.requestMatching == [.URL, .HTTPMethod] {
50+
guard
51+
interactionRequest.URL == request.URL &&
52+
interactionRequest.HTTPMethod == request.HTTPMethod
53+
else {
54+
continue
55+
}
56+
57+
return interaction
58+
}
59+
60+
if cassetteOptions.requestMatching == [.URL, .HTTPBody] {
61+
guard
62+
interactionRequest.URL == request.URL &&
63+
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
64+
else {
65+
continue
66+
}
67+
68+
return interaction
69+
}
70+
71+
if cassetteOptions.requestMatching == [.Path, .HTTPMethod] {
72+
guard
73+
interactionRequest.URL?.relativePath == request.URL?.relativePath &&
74+
interactionRequest.HTTPMethod == request.HTTPMethod
75+
else {
76+
continue
77+
}
78+
79+
return interaction
80+
}
81+
82+
if cassetteOptions.requestMatching == [.Path, .HTTPBody] {
83+
guard
84+
interactionRequest.URL?.relativePath == request.URL?.relativePath &&
85+
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
86+
else {
87+
continue
88+
}
89+
90+
return interaction
91+
}
2492

25-
// Note: We don't check headers right now
26-
if interactionRequest.HTTPMethod == request.HTTPMethod && interactionRequest.URL == request.URL && interactionRequest.hasHTTPBodyEqualToThatOfRequest(request) {
93+
if cassetteOptions.requestMatching == [.HTTPMethod, .HTTPBody] {
94+
guard
95+
interactionRequest.HTTPMethod == request.HTTPMethod &&
96+
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
97+
else {
98+
continue
99+
}
100+
101+
return interaction
102+
}
103+
104+
if cassetteOptions.requestMatching == [.URL] {
105+
guard
106+
interactionRequest.URL == request.URL
107+
else {
108+
continue
109+
}
110+
111+
return interaction
112+
}
113+
114+
if cassetteOptions.requestMatching == [.Path] {
115+
guard
116+
interactionRequest.URL?.relativePath == request.URL?.relativePath
117+
else {
118+
continue
119+
}
120+
121+
return interaction
122+
}
123+
124+
if cassetteOptions.requestMatching == [.HTTPMethod] {
125+
guard
126+
interactionRequest.HTTPMethod == request.HTTPMethod
127+
else {
128+
continue
129+
}
130+
131+
return interaction
132+
}
133+
134+
if cassetteOptions.requestMatching == [.HTTPBody] {
135+
guard
136+
interactionRequest.hasHTTPBodyEqualToThatOfRequest(request)
137+
else {
138+
continue
139+
}
140+
27141
return interaction
28142
}
29143
}
144+
30145
return nil
31146
}
32147
}
33148

34-
35149
extension Cassette {
36150
var dictionary: [String: AnyObject] {
37151
return [
@@ -40,10 +154,11 @@ extension Cassette {
40154
]
41155
}
42156

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

46160
self.name = name
161+
self.cassetteOptions = cassetteOptions
47162

48163
if let array = dictionary["interactions"] as? [[String: AnyObject]] {
49164
interactions = array.flatMap { Interaction(dictionary: $0) }

DVR/CassetteOptions.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// CassetteOptions.swift
3+
// DVR
4+
//
5+
// Created by Peter Nicholls on 6/07/2016.
6+
// Copyright © 2016 Venmo. All rights reserved.
7+
//
8+
9+
public struct CassetteOptions {
10+
11+
// MARK: - Properties
12+
13+
public let requestMatching: RequestMatching
14+
15+
// MARK: - Initializers
16+
17+
public init(requestMatching: RequestMatching = [.URL, .Path, .HTTPMethod, .HTTPBody]) {
18+
self.requestMatching = requestMatching
19+
}
20+
}

DVR/RequestMatchingOptions.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// RequestMatching.swift
3+
// DVR
4+
//
5+
// Created by Peter Nicholls on 6/07/2016.
6+
// Copyright © 2016 Venmo. All rights reserved.
7+
//
8+
9+
public struct RequestMatching : OptionSetType {
10+
11+
// MARK: - Properties
12+
13+
private enum Method : Int {
14+
case URL = 1, Path = 2, HTTPMethod = 4, HTTPBody = 8
15+
}
16+
17+
public let rawValue : Int
18+
19+
public static let URL = RequestMatching(Method.URL)
20+
public static let Path = RequestMatching(Method.Path)
21+
public static let HTTPMethod = RequestMatching(Method.HTTPMethod)
22+
public static let HTTPBody = RequestMatching(Method.HTTPBody)
23+
24+
// MARK: - Initializers
25+
26+
public init(rawValue: Int) {
27+
self.rawValue = rawValue
28+
}
29+
30+
private init(_ direction: Method) {
31+
self.rawValue = direction.rawValue
32+
}
33+
}

DVR/Session.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public class Session: NSURLSession {
88
public let cassetteName: String
99
public let backingSession: NSURLSession
1010
public var recordingEnabled = true
11-
11+
public let cassetteOptions: CassetteOptions
12+
1213
private let testBundle: NSBundle
1314

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

2425
// MARK: - Initializers
2526

26-
public init(outputDirectory: String = "~/Desktop/DVR/", cassetteName: String, testBundle: NSBundle = NSBundle.allBundles().filter() { $0.bundlePath.hasSuffix(".xctest") }.first!, backingSession: NSURLSession = NSURLSession.sharedSession()) {
27+
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()) {
2728
self.outputDirectory = outputDirectory
2829
self.cassetteName = cassetteName
2930
self.testBundle = testBundle
3031
self.backingSession = backingSession
32+
self.cassetteOptions = cassetteOptions
33+
3134
super.init()
3235
}
3336

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

119-
return Cassette(dictionary: json)
122+
return Cassette(dictionary: json, cassetteOptions: cassetteOptions)
120123
}
121124

122125
func finishTask(task: NSURLSessionTask, interaction: Interaction, playback: Bool) {
@@ -195,7 +198,7 @@ public class Session: NSURLSession {
195198
}
196199
}
197200

198-
let cassette = Cassette(name: cassetteName, interactions: interactions)
201+
let cassette = Cassette(name: cassetteName, interactions: interactions, cassetteOptions: cassetteOptions)
199202

200203
// Persist
201204

DVR/SessionDataTask.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class SessionDataTask: NSURLSessionDataTask {
3737

3838
override func resume() {
3939
let cassette = session.cassette
40-
40+
4141
// Find interaction
4242
if let interaction = session.cassette?.interactionForRequest(request) {
4343
self.interaction = interaction
@@ -47,12 +47,14 @@ class SessionDataTask: NSURLSessionDataTask {
4747
completion(interaction.responseData, interaction.response, nil)
4848
}
4949
}
50+
5051
session.finishTask(self, interaction: interaction, playback: true)
5152
return
5253
}
53-
54+
5455
if cassette != nil {
5556
print("[DVR] Invalid request. The request was not found in the cassette.")
57+
print("[DVR] Request: ", request)
5658
abort()
5759
}
5860

DVR/Tests/SessionTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,25 @@ class SessionTests: XCTestCase {
133133

134134
waitForExpectationsWithTimeout(1, handler: nil)
135135
}
136+
137+
func testDataTaskWithHTTPBodyRequestMatchingCassetteOptions() {
138+
let cassetteOptions = CassetteOptions(requestMatching: [.HTTPBody])
139+
let session = Session(cassetteName: "example", cassetteOptions: cassetteOptions)
140+
141+
session.recordingEnabled = false
142+
let expectation = expectationWithDescription("Network")
143+
144+
session.dataTaskWithRequest(request) { data, response, error in
145+
XCTAssertEqual("hello", String(data: data!, encoding: NSUTF8StringEncoding))
146+
147+
let HTTPResponse = response as! NSHTTPURLResponse
148+
XCTAssertEqual(200, HTTPResponse.statusCode)
149+
150+
expectation.fulfill()
151+
}.resume()
152+
153+
waitForExpectationsWithTimeout(1, handler: nil)
154+
}
136155

137156
func testTaskDelegate() {
138157
class Delegate: NSObject, NSURLSessionTaskDelegate {

0 commit comments

Comments
 (0)