Skip to content

Commit afbc6bd

Browse files
committed
Add test for header fields being sent to the server.
1 parent ca157bc commit afbc6bd

File tree

3 files changed

+131
-4
lines changed

3 files changed

+131
-4
lines changed

Foundation/NSURLSession/Configuration.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,6 @@ internal extension NSURLSession.Configuration {
104104
HTTPAdditionalHeaders?.forEach {
105105
request.setValue($0.1, forHTTPHeaderField: $0.0)
106106
}
107-
//TODO:
108-
// request.allowsCellularAccess = allowsCellularAccess
109107
}
110108
func setCookies(request request: NSMutableURLRequest) {
111109
if HTTPShouldSetCookies {

TestFoundation/HTTPServer.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,14 @@ struct HTTPRequest {
312312
}
313313
}
314314

315+
extension HTTPRequest {
316+
func forEachHeaderField(@noescape handler: (name: String, value: String) -> ()) {
317+
message.headers.forEach {
318+
handler(name: $0.name, value: $0.value)
319+
}
320+
}
321+
}
322+
315323
/// A HTTP response
316324
///
317325
/// - SeeAlso: RFC 2616 Section 6

TestFoundation/TestNSURLSession.swift

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,74 @@ extension TaskDelegate: NSURLSessionDataDelegate {
115115
}
116116
}
117117

118+
//MARK: - Header Field Helpers
118119

120+
private struct HeaderFields {
121+
let fields: [(name: String, value: String)]
122+
}
123+
extension HeaderFields {
124+
func value(forField name: String) -> String? {
125+
let lowercasedName = name.lowercased()
126+
return fields.index(where: { lowercasedName == $0.name.lowercased() }).map { fields[$0].value }
127+
}
128+
var count: Int { return fields.count }
129+
}
130+
extension HeaderFields {
131+
func hasValue(forField name: String) -> Bool {
132+
return value(forField: name) != nil
133+
}
134+
}
135+
136+
137+
/// Tests that the header fields with the given names exist.
138+
private func AssertHeaderFieldsExist(@autoclosure expression1: () throws -> HeaderFields, @autoclosure _ expression2: () throws -> [String], @autoclosure _ message: () -> String = "", file: StaticString = #file, line: UInt = #line) {
139+
let m = message()
140+
do {
141+
let f = try expression1()
142+
try expression2().forEach {
143+
if !f.hasValue(forField: $0) {
144+
XCTFail(file: file, line: line, "Field '\($0)' is missing. " + m)
145+
}
146+
}
147+
} catch let e { XCTFail(file: file, line: line, "\(m) -- \(e)") }
148+
}
149+
/// Tests that header fields only exist for the given names and nothing else.
150+
private func AssertHeaderFieldNamesEqual(@autoclosure expression1: () throws -> HeaderFields, @autoclosure _ expression2: () throws -> [String], @autoclosure _ message: () -> String = "", file: StaticString = #file, line: UInt = #line) {
151+
let m = message()
152+
do {
153+
let fields = try expression1()
154+
var extraNames = fields.fields.map { $0.name }
155+
try expression2().forEach { field in
156+
if let idx = extraNames.index(where: { field.lowercased() == $0.lowercased() }) {
157+
extraNames.remove(at: idx)
158+
} else {
159+
XCTFail(file: file, line: line, "Header field '\(field)' is missing. " + m)
160+
}
161+
}
162+
extraNames.forEach {
163+
let value = fields.value(forField: $0) ?? ""
164+
XCTFail(file: file, line: line, "Extranous header field '\($0): \(value)'. " + m)
165+
}
166+
167+
} catch let e { XCTFail(file: file, line: line, "\(m) -- \(e)") }
168+
}
169+
/// Tests the the header fields match those in the dictionary, *and* that all
170+
/// fields in the dictionary exist. But the headers might have more values.
171+
private func AssertHeaderFieldsEqual(@autoclosure expression1: () throws -> HeaderFields, @autoclosure _ expression2: () throws -> [String:String], @autoclosure _ message: () -> String = "", file: StaticString = #file, line: UInt = #line) {
172+
let m = message()
173+
do {
174+
let f = try expression1()
175+
try expression2().forEach { field in
176+
if let v = f.value(forField: field.0) {
177+
XCTAssertEqual(v, field.1, file: file, line: line, "Header field '\(field.0)'. \(m)")
178+
} else {
179+
XCTFail(file: file, line: line, "Header field '\(field.0)' is missing. \(m)")
180+
}
181+
}
182+
} catch let e { XCTFail(file: file, line: line, "\(m) -- \(e)") }
183+
}
184+
185+
//MARK: - Tests
119186

120187
class TestNSURLSession : XCTestCase {
121188
static var allTests: [(String, TestNSURLSession -> () throws -> Void)] {
@@ -130,6 +197,8 @@ class TestNSURLSession : XCTestCase {
130197
("test_functional_POST_fromData", test_functional_POST_fromData),
131198
("test_functional_POST_fromFile", test_functional_POST_fromFile),
132199
("test_functional_POST_fromFileThatDoesNotExist", test_functional_POST_fromFileThatDoesNotExist),
200+
201+
("test_functional_defaultHeaders", test_functional_defaultHeaders),
133202
]
134203
}
135204

@@ -146,8 +215,8 @@ class TestNSURLSession : XCTestCase {
146215
let task = sut.dataTaskWithURL(NSURL(string: "http://swift.org/")!)
147216
XCTAssertEqual(task.state, NSURLSessionTaskState.Suspended)
148217
}
149-
150-
func createServer(file: StaticString = #file, line: UInt = #line, handler: (HTTPRequest) -> (HTTPResponse)) -> (SocketServer, NSURL)? {
218+
/// Create an HTTP server and return the server and its base URL.
219+
private func createServer(file file: StaticString = #file, line: UInt = #line, handler: (HTTPRequest) -> HTTPResponse) -> (SocketServer, NSURL)? {
151220
let q = dispatch_queue_create("test_2", DISPATCH_QUEUE_SERIAL)
152221
let server: SocketServer
153222
do {
@@ -167,6 +236,30 @@ class TestNSURLSession : XCTestCase {
167236
c.path = "/"
168237
return (server, c.URL!)
169238
}
239+
/// Creates an HTTP server with a block that gets all headers.
240+
private func createExtractHeaderServer(file file: StaticString = #file, line: UInt = #line, headerHandler: (HeaderFields) -> ()) -> (SocketServer, NSURL)? {
241+
return createServer(file: file, line: line) { request in
242+
var headerFields: [(name: String, value: String)] = []
243+
request.forEachHeaderField {
244+
headerFields.append((name: $0, value: $1))
245+
}
246+
headerHandler(HeaderFields(fields: headerFields))
247+
return HTTPResponse(statusCode: 200, additionalHeaderFields: [], body: "")
248+
}
249+
}
250+
/// Helper function that creates a session with a delegate and calls the
251+
/// given closure with all three wrapped in a `withExtendedLifetime()` call.
252+
private func with(server server: SocketServer, @noescape testClosure: (NSURLSession, TaskDelegate) -> ()) {
253+
withExtendedLifetime(server) {
254+
let delegate = TaskDelegate()
255+
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: delegate, delegateQueue: queue)
256+
withExtendedLifetime(delegate) {
257+
withExtendedLifetime(session) {
258+
testClosure(session, delegate)
259+
}
260+
}
261+
}
262+
}
170263
}
171264

172265
/// Create test data with a well-defined pattern.
@@ -545,9 +638,37 @@ extension TestNSURLSession {
545638
}
546639
}
547640

641+
548642
//TODO: Uploading data with completion handler
549643
// public func uploadTaskWithRequest(request: NSURLRequest, fromFile fileURL: NSURL, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionUploadTask
550644
// public func uploadTaskWithRequest(request: NSURLRequest, fromData bodyData: NSData?, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionUploadTask
645+
646+
func test_functional_defaultHeaders() {
647+
var baseURL: NSURL?
648+
guard let (server, _baseURL) = createExtractHeaderServer(headerHandler: { headers in
649+
let port = Int(baseURL?.port ?? 0)
650+
let expected = ["Host": "\(baseURL?.host ?? ""):\(String(port))",
651+
"Accept": "*/*",
652+
"Accept-Language": "en-us",
653+
"Accept-Encoding": "gzip, deflate",
654+
]
655+
AssertHeaderFieldsEqual(headers, expected)
656+
AssertHeaderFieldNamesEqual(headers, ["Host", "Accept", "Accept-Language", "Accept-Encoding", "Connection", "User-Agent"])
657+
}) else { XCTFail(); return }
658+
baseURL = _baseURL
659+
with(server: server) { (session, delegate) in
660+
let URL = NSURL(string: "/bar", relativeToURL: baseURL)!
661+
662+
let completionExpectation = expectationWithDescription("Task did complete")
663+
let task = session.dataTaskWithURL(URL, completionHandler: { (_, _, _) in
664+
completionExpectation.fulfill()
665+
})
666+
667+
task.resume()
668+
waitForExpectationsWithTimeout(1, handler: nil)
669+
XCTAssertEqual(task.state, NSURLSessionTaskState.Completed)
670+
}
671+
}
551672
}
552673

553674
private extension HTTPResponse {

0 commit comments

Comments
 (0)