diff --git a/.gitignore b/.gitignore
index e0ae851..cea0fd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# Xcode
#
build/
+.build
*.pbxuser
!default.pbxuser
*.mode1v3
diff --git a/.swiftpm/.DS_Store b/.swiftpm/.DS_Store
new file mode 100644
index 0000000..84eb2cd
Binary files /dev/null and b/.swiftpm/.DS_Store differ
diff --git a/.swiftpm/xcode/.DS_Store b/.swiftpm/xcode/.DS_Store
new file mode 100644
index 0000000..0998a6f
Binary files /dev/null and b/.swiftpm/xcode/.DS_Store differ
diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/.travis.yml b/.travis.yml
index ad100f7..dad9388 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,6 @@
-matrix:
- include:
- - os: osx
- osx_image: xcode10.2
+language: swift
+osx_image: xcode12u
script:
- - cd URITemplate && swift package generate-xcodeproj && cd ..
- - xcodebuild -project Mockingjay.xcodeproj -scheme Mockingjay test
- - pod lib lint --quick
+- swift --version
+- swift package update
+- swift test
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32dc9bc..cbd402f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Mockingjay Changelog
+## TBD
+
+### Enhancements
+
+- Support for redirect responses (3xx).
+
## 3.0.0-alpha.1
### Breaking
diff --git a/Mockingjay.podspec b/Mockingjay.podspec
index 0ae36a8..668eadf 100644
--- a/Mockingjay.podspec
+++ b/Mockingjay.podspec
@@ -18,7 +18,7 @@ Pod::Spec.new do |spec|
'Sources/Mockingjay/MockingjayProtocol.swift',
'Sources/Mockingjay/{Matchers,Builders}.swift',
'Sources/Mockingjay/NSURLSessionConfiguration.swift',
- 'Sources/Mockingjay/MockingjayURLSessionConfiguration.m'
+ 'Sources/Mockingjay/MockingjayURLSessionConfiguration.{m,swift}'
end
spec.subspec 'XCTest' do |xctest_spec|
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..9400eef
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,39 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "Mockingjay",
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "Mockingjay",
+ targets: ["Mockingjay"]),
+ ],
+ dependencies: [
+ // Dependencies declare other packages that this package depends on.
+ .package(name: "URITemplate", url: "https://github.com/kylef/URITemplate.swift.git", .branch("master")),
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "Mockingjay",
+ dependencies: [
+ .product(name: "URITemplate", package: "URITemplate"),
+ ],
+ exclude: ["Info.plist"]
+ ),
+
+ .testTarget(
+ name: "MockingjayTests",
+ dependencies: [
+ "Mockingjay",
+ .product(name: "URITemplate", package: "URITemplate"),
+ ],
+ exclude: ["Info.plist"],
+ resources: [.process("TestAudio.m4a")]
+ ),
+ ]
+)
diff --git a/Sources/Mockingjay/Builders.swift b/Sources/Mockingjay/Builders.swift
index d390a9b..e04b958 100644
--- a/Sources/Mockingjay/Builders.swift
+++ b/Sources/Mockingjay/Builders.swift
@@ -10,9 +10,17 @@ import Foundation
// Collection of generic builders
+internal struct MockingjayFailure: Error {}
+
/// Generic builder for returning a failure
-public func failure(_ error: NSError) -> (_ request: URLRequest) -> Response {
- return { _ in return .failure(error) }
+public func failure(_ error: Error? = nil) -> (_ request: URLRequest) -> Response {
+ return { _ in
+ if let error = error {
+ return .failure(error)
+ }
+
+ return .failure(MockingjayFailure())
+ }
}
public func http(_ status:Int = 200, headers:[String:String]? = nil, download:Download=nil) -> (_ request: URLRequest) -> Response {
@@ -24,6 +32,22 @@ public func http(_ status:Int = 200, headers:[String:String]? = nil, download:Do
return .failure(NSError(domain: NSExceptionName.internalInconsistencyException.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to construct response for stub."]))
}
}
+public func content(_ data: Data, headers: [String:String]? = nil) -> (_ request: URLRequest) -> Response {
+ return http(200, headers: headers, download: .content(data))
+}
+
+public func text(_ body: String, using encoding: String.Encoding, status: Int = 200, headers: [String: String]? = nil) -> (_ request: URLRequest) -> Response {
+ var headers = headers ?? [String:String]()
+ if headers["Content-Type"] == nil && encoding == .utf8 {
+ headers["Content-Type"] = "text/plain; charset=utf-8"
+ }
+
+ if let data = body.data(using: encoding) {
+ return http(status, headers: headers, download: .content(data))
+ }
+
+ return failure()
+}
public func json(_ body: Any, status:Int = 200, headers:[String:String]? = nil) -> (_ request: URLRequest) -> Response {
return { (request:URLRequest) in
@@ -46,3 +70,13 @@ public func jsonData(_ data: Data, status: Int = 200, headers: [String:String]?
return http(status, headers: headers, download: .content(data))(request)
}
}
+
+public func redirect(to url: URL, status: Int = 301, headers: [String:String]? = nil) -> (_ request: URLRequest) -> Response {
+ var headers = headers ?? [:]
+ headers["Location"] = url.absoluteString
+
+ let resoponse = HTTPURLResponse(url: url, statusCode: status, httpVersion: nil, headerFields: headers)!
+ return { request in
+ return .success(resoponse, .noContent)
+ }
+}
diff --git a/Sources/Mockingjay/Mockingjay.swift b/Sources/Mockingjay/Mockingjay.swift
index 4bd8e17..b833723 100644
--- a/Sources/Mockingjay/Mockingjay.swift
+++ b/Sources/Mockingjay/Mockingjay.swift
@@ -38,13 +38,13 @@ public func ==(lhs:Download, rhs:Download) -> Bool {
public enum Response : Equatable {
case success(URLResponse, Download)
- case failure(NSError)
+ case failure(Error)
}
public func ==(lhs:Response, rhs:Response) -> Bool {
switch (lhs, rhs) {
case let (.failure(lhsError), .failure(rhsError)):
- return lhsError == rhsError
+ return (lhsError as NSError) == (rhsError as NSError)
case let (.success(lhsResponse, lhsDownload), .success(rhsResponse, rhsDownload)):
return lhsResponse == rhsResponse && lhsDownload == rhsDownload
default:
diff --git a/Sources/Mockingjay/MockingjayProtocol.swift b/Sources/Mockingjay/MockingjayProtocol.swift
index 0d224e9..6cf6ee5 100644
--- a/Sources/Mockingjay/MockingjayProtocol.swift
+++ b/Sources/Mockingjay/MockingjayProtocol.swift
@@ -40,7 +40,9 @@ public class MockingjayProtocol: URLProtocol {
stubs.append(stub)
if !registered {
- URLProtocol.registerClass(MockingjayProtocol.self)
+ MockingjayURLSessionConfiguration.swizzleDefaultSessionConfiguration()
+ URLProtocol.registerClass(MockingjayProtocol.self)
+ registered = true
}
return stub
@@ -109,6 +111,23 @@ public class MockingjayProtocol: URLProtocol {
}
// MARK: Private Methods
+
+ func isRedirect(request: URLRequest, response: URLResponse) -> URLRequest? {
+ guard
+ let response = response as? HTTPURLResponse,
+ response.statusCode == 301 || response.statusCode == 302 || response.statusCode == 303 || response.statusCode == 307,
+ let location = response.allHeaderFields["Location"] as? String,
+ let locationURL = URL(string: location, relativeTo: response.url)
+ else { return nil }
+
+ if response.statusCode == 307 {
+ var redirectRequest = request
+ redirectRequest.url = locationURL
+ return redirectRequest
+ }
+
+ return URLRequest(url: locationURL)
+ }
fileprivate func sendResponse(_ response: Response) {
switch response {
@@ -116,6 +135,11 @@ public class MockingjayProtocol: URLProtocol {
client?.urlProtocol(self, didFailWithError: error)
case .success(var response, let download):
let headers = self.request.allHTTPHeaderFields
+
+ if let redirectRequest = isRedirect(request: self.request, response: response) {
+ client?.urlProtocol(self, wasRedirectedTo: redirectRequest, redirectResponse: response)
+ return
+ }
switch(download) {
case .content(var data):
diff --git a/Sources/Mockingjay/MockingjayURLSessionConfiguration.m b/Sources/Mockingjay/MockingjayURLSessionConfiguration.m
deleted file mode 100644
index f83735a..0000000
--- a/Sources/Mockingjay/MockingjayURLSessionConfiguration.m
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// MockingjayURLSessionConfiguration.m
-// Mockingjay
-//
-// Created by Kyle Fuller on 10/05/2016.
-// Copyright © 2016 Cocode. All rights reserved.
-//
-
-#import
-#import
-
-
-@interface MockingjayURLConfiguration : NSObject
-
-@end
-
-@implementation MockingjayURLConfiguration
-
-+ (void)load {
- [NSURLSessionConfiguration mockingjaySwizzleDefaultSessionConfiguration];
-}
-
-@end
\ No newline at end of file
diff --git a/Sources/Mockingjay/MockingjayURLSessionConfiguration.swift b/Sources/Mockingjay/MockingjayURLSessionConfiguration.swift
new file mode 100644
index 0000000..a7a44f4
--- /dev/null
+++ b/Sources/Mockingjay/MockingjayURLSessionConfiguration.swift
@@ -0,0 +1,16 @@
+//
+// MockingjayURLSessionConfiguration.m
+// Mockingjay
+//
+// Created by Kyle Fuller on 10/05/2016.
+// Copyright © 2016 Cocode. All rights reserved.
+//
+
+import Foundation
+
+public class MockingjayURLSessionConfiguration: NSObject {
+
+ public class func swizzleDefaultSessionConfiguration() {
+ URLSessionConfiguration.mockingjaySwizzleDefaultSessionConfiguration()
+ }
+}
diff --git a/Sources/Mockingjay/XCTest.swift b/Sources/Mockingjay/XCTest.swift
index 8e2905a..ad0e2f7 100644
--- a/Sources/Mockingjay/XCTest.swift
+++ b/Sources/Mockingjay/XCTest.swift
@@ -10,7 +10,7 @@ import ObjectiveC
import XCTest
let swizzleTearDown: Void = {
- let tearDown = class_getInstanceMethod(XCTest.self, #selector(XCTest.tearDown))
+ let tearDown = class_getInstanceMethod(XCTest.self, #selector(XCTest.tearDown as (XCTest) -> () -> Void))
let mockingjayTearDown = class_getInstanceMethod(XCTest.self, #selector(XCTest.mockingjayTearDown))
method_exchangeImplementations(tearDown!, mockingjayTearDown!)
}()
diff --git a/Tests/MockingjayTests/BuildersTests.swift b/Tests/MockingjayTests/BuildersTests.swift
index 6b97f97..a440471 100644
--- a/Tests/MockingjayTests/BuildersTests.swift
+++ b/Tests/MockingjayTests/BuildersTests.swift
@@ -20,6 +20,19 @@ class FailureBuilderTests : XCTestCase {
XCTAssertEqual(response, Response.failure(error))
}
+
+ func testUnspecifiedFailure() {
+ let request = URLRequest(url: URL(string: "http://test.com/")!)
+
+ let response = failure()(request)
+
+ switch response {
+ case .success(_, _):
+ XCTFail("Unexpected success")
+ case .failure(_):
+ break
+ }
+ }
func testHTTP() {
let request = URLRequest(url: URL(string: "http://test.com/")!)
@@ -51,10 +64,35 @@ class FailureBuilderTests : XCTestCase {
XCTFail("Test Failure")
}
case let .failure(error):
- XCTFail("Test Failure: " + error.debugDescription)
+ XCTFail("Test Failure: " + (error as NSError).debugDescription)
}
}
-
+
+ func testText() {
+ let request = URLRequest(url: URL(string: "http://test.com/")!)
+ let response = text("Hello World", using: .utf8)(request)
+
+ switch response {
+ case let .success(response, download):
+ switch download {
+ case .content(let data):
+ if let response = response as? HTTPURLResponse {
+ XCTAssertEqual(response.statusCode, 200)
+ XCTAssertEqual(response.mimeType, "text/plain")
+ XCTAssertEqual(response.textEncodingName, "utf-8")
+ let body = NSString(data:data, encoding: String.Encoding.utf8.rawValue)
+ XCTAssertEqual(body, "Hello World")
+ } else {
+ XCTFail("Test Failure")
+ }
+ default:
+ XCTFail("Test Failure")
+ }
+ default:
+ XCTFail("Test Failure")
+ }
+ }
+
func testJSON() {
let request = URLRequest(url: URL(string: "http://test.com/")!)
let response = json(["A"])(request)
@@ -107,4 +145,42 @@ class FailureBuilderTests : XCTestCase {
XCTFail("Test Failure")
}
}
+
+ func testRedirect() {
+ let request = URLRequest(url: URL(string: "http://example.com")!)
+ let response = redirect(to: URL(string: "https://example.com")!)(request)
+
+ switch response {
+ case let .success(response, _):
+ guard let response = response as? HTTPURLResponse else {
+ XCTFail("Test Failure")
+ return
+ }
+
+
+ XCTAssertEqual(response.statusCode, 301)
+ XCTAssertEqual(response.allHeaderFields["Location"] as? String, "https://example.com")
+ default:
+ XCTFail("Test Failure")
+ }
+ }
+
+ func testRelativeRedirect() {
+ let request = URLRequest(url: URL(string: "https://example.com")!)
+ let response = redirect(to: URL(string: "/authorize")!)(request)
+
+ switch response {
+ case let .success(response, _):
+ guard let response = response as? HTTPURLResponse else {
+ XCTFail("Test Failure")
+ return
+ }
+
+
+ XCTAssertEqual(response.statusCode, 301)
+ XCTAssertEqual(response.allHeaderFields["Location"] as? String, "/authorize")
+ default:
+ XCTFail("Test Failure")
+ }
+ }
}
diff --git a/Tests/MockingjayTests/MockingjayAsyncProtocolTests.swift b/Tests/MockingjayTests/MockingjayAsyncProtocolTests.swift
index 555e56d..e5fe8dd 100644
--- a/Tests/MockingjayTests/MockingjayAsyncProtocolTests.swift
+++ b/Tests/MockingjayTests/MockingjayAsyncProtocolTests.swift
@@ -61,7 +61,7 @@ class MockingjayAsyncProtocolTests: XCTestCase, URLSessionDataDelegate {
func testDownloadOfAudioFileInChunks() {
let request = URLRequest(url: URL(string: "https://fuller.li/")!)
- let path = Bundle(for: self.classForCoder).path(forResource: "TestAudio", ofType: "m4a")
+ let path = Bundle.module.path(forResource: "TestAudio", ofType: "m4a")
let data = try! Data(contentsOf: URL(fileURLWithPath: path!))
let stubResponse = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "1.1", headerFields: ["Content-Length" : String(data.count)])!
@@ -91,14 +91,14 @@ class MockingjayAsyncProtocolTests: XCTestCase, URLSessionDataDelegate {
let length = 100000
var request = URLRequest(url: URL(string: "https://fuller.li/")!)
request.addValue("bytes=50000-149999", forHTTPHeaderField: "Range")
- let path = Bundle(for: self.classForCoder).path(forResource: "TestAudio", ofType: "m4a")
+ let path = Bundle.module.path(forResource: "TestAudio", ofType: "m4a")
let data = try! Data(contentsOf: URL(fileURLWithPath: path!))
let stubResponse = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "1.1", headerFields: ["Content-Length" : String(length)])!
MockingjayProtocol.addStub(matcher: { (requestedRequest) -> (Bool) in
return true
}) { (request) -> (Response) in
- return Response.success(stubResponse, .streamContent(data: data, inChunksOf: 2000))
+ return Response.success(stubResponse, .streamContent(data: data, inChunksOf: 2000))
}
let urlSession = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.current)
diff --git a/Tests/MockingjayTests/MockingjayProtocolTests.swift b/Tests/MockingjayTests/MockingjayProtocolTests.swift
index 43960ec..871c8f3 100644
--- a/Tests/MockingjayTests/MockingjayProtocolTests.swift
+++ b/Tests/MockingjayTests/MockingjayProtocolTests.swift
@@ -165,5 +165,60 @@ class MockingjayProtocolTests : XCTestCase {
XCTAssert(startDate.addingTimeInterval(0.95).compare(Date()) == .orderedAscending)
}
-
+
+ func testRedirect() {
+ let request = URLRequest(url: URL(string: "http://example.com")!)
+
+ MockingjayProtocol.addStub(
+ matcher: { $0.url?.absoluteString == "https://example.com" },
+ builder: http(204, download: .noContent)
+ )
+
+ MockingjayProtocol.addStub(
+ matcher: { $0.url?.absoluteString == "http://example.com" },
+ builder: http(301, headers: ["Location": "https://example.com"], download: .noContent)
+ )
+
+ let expectation = self.expectation(description: #function)
+
+ urlSession = URLSession(configuration: URLSessionConfiguration.default)
+ let dataTask = urlSession.dataTask(with: request) { _, response, _ in
+ XCTAssertEqual(response?.url?.absoluteString, "https://example.com")
+ expectation.fulfill()
+ }
+ dataTask.resume()
+
+ waitForExpectations(timeout: 2.0, handler: nil)
+ }
+
+ func testRedirect307() {
+ var request = URLRequest(url: URL(string: "http://example.com")!)
+ request.httpMethod = "PUT"
+ request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
+
+ MockingjayProtocol.addStub(
+ matcher: { $0.url?.absoluteString == "https://example.com" },
+ builder: http(204, download: .noContent)
+ )
+
+ MockingjayProtocol.addStub(
+ matcher: { $0.url?.absoluteString == "http://example.com" },
+ builder: { request in
+ XCTAssertEqual(request.httpMethod, "PUT")
+ XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], "text/plain")
+ return http(307, headers: ["Location": "https://example.com"], download: .noContent)(request)
+ }
+ )
+
+ let expectation = self.expectation(description: #function)
+
+ urlSession = URLSession(configuration: URLSessionConfiguration.default)
+ let dataTask = urlSession.dataTask(with: request) { _, response, _ in
+ XCTAssertEqual(response?.url?.absoluteString, "https://example.com")
+ expectation.fulfill()
+ }
+ dataTask.resume()
+
+ waitForExpectations(timeout: 2.0, handler: nil)
+ }
}