diff --git a/README.md b/README.md index a938ae7..72c8400 100644 --- a/README.md +++ b/README.md @@ -216,8 +216,18 @@ Mock(url: URL(string: "https://wetransfer.com/redirect")!, contentType: .json, s As the Mocker catches all URLs by default when registered, you might end up with a `fatalError` thrown in cases you don't need a mocked request. In that case, you can ignore the URL: ```swift -let ignoredURL = URL(string: "www.wetransfer.com")! +let ignoredURL = URL(string: "https://www.wetransfer.com")! + +// Ignore any requests that exactly match the URL Mocker.ignore(ignoredURL) + +// Ignore any requests that match the URL, with any query parameters +// e.g. https://www.wetransfer.com?foo=bar would be ignored +Mocker.ignore(ignoredURL, matchType: .ignoreQuery) + +// Ignore any requests that begin with the URL +// e.g. https://www.wetransfer.com/api/v1 would be ignored +Mocker.ignore(ignoredURL, matchType: .prefix) ``` However, if you need the Mocker to catch only mocked URLs and ignore every other URL, you can set the `mode` attribute to `.optin`. diff --git a/Sources/Mocker/Mock.swift b/Sources/Mocker/Mock.swift index 3359aa5..d1407b2 100644 --- a/Sources/Mocker/Mock.swift +++ b/Sources/Mocker/Mock.swift @@ -331,11 +331,3 @@ public struct Mock: Equatable { return lhs.request.url!.absoluteString == rhs.request.url!.absoluteString && lhsHTTPMethods == rhsHTTPMethods } } - -extension URL { - /// Returns the base URL string build with the scheme, host and path. "https://www.wetransfer.com/v1/test?param=test" would be "https://www.wetransfer.com/v1/test". - var baseString: String? { - guard let scheme = scheme, let host = host else { return nil } - return scheme + "://" + host + path - } -} diff --git a/Sources/Mocker/Mocker.swift b/Sources/Mocker/Mocker.swift index e1d2bb1..70a710c 100644 --- a/Sources/Mocker/Mocker.swift +++ b/Sources/Mocker/Mocker.swift @@ -15,18 +15,14 @@ import FoundationNetworking public struct Mocker { private struct IgnoredRule: Equatable { let urlToIgnore: URL - let ignoreQuery: Bool + let matchType: URLMatchType /// Checks if the passed URL should be ignored. /// /// - Parameter url: The URL to check for. /// - Returns: `true` if it should be ignored, `false` if the URL doesn't correspond to ignored rules. func shouldIgnore(_ url: URL) -> Bool { - if ignoreQuery { - return urlToIgnore.baseString == url.baseString - } - - return urlToIgnore.absoluteString == url.absoluteString + url.matches(urlToIgnore, matchType: matchType) } } @@ -92,11 +88,23 @@ public struct Mocker { /// Register an URL to ignore for mocking. This will let the URL work as if the Mocker doesn't exist. /// - /// - Parameter url: The URL to mock. + /// - Parameter url: The URL to ignore. /// - Parameter ignoreQuery: If `true`, checking the URL will ignore the query and match only for the scheme, host and path. Defaults to `false`. - public static func ignore(_ url: URL, ignoreQuery: Bool = false) { + @available(*, deprecated, renamed: "ignore(_:matchType:)") + public static func ignore(_ url: URL, ignoreQuery: Bool) { + shared.queue.async(flags: .barrier) { + let rule = IgnoredRule(urlToIgnore: url, matchType: ignoreQuery ? .ignoreQuery : .full) + shared.ignoredRules.append(rule) + } + } + + /// Register an URL to ignore for mocking. This will let the URL work as if the Mocker doesn't exist. + /// + /// - Parameter url: The URL to ignore. + /// - Parameter matchType: The approach that will be used to determine whether URLs match the provided URL. Defaults to `full`. + public static func ignore(_ url: URL, matchType: URLMatchType = .full) { shared.queue.async(flags: .barrier) { - let rule = IgnoredRule(urlToIgnore: url, ignoreQuery: ignoreQuery) + let rule = IgnoredRule(urlToIgnore: url, matchType: matchType) shared.ignoredRules.append(rule) } } diff --git a/Sources/Mocker/URLMatchType.swift b/Sources/Mocker/URLMatchType.swift new file mode 100644 index 0000000..55a4353 --- /dev/null +++ b/Sources/Mocker/URLMatchType.swift @@ -0,0 +1,44 @@ +// +// URLMatchType.swift +// Mocker +// +// Created by Brent Whitman on 2024-04-18. +// + +import Foundation + +/// How to check if one URL matches another. +public enum URLMatchType { + /// Matches the full URL, including the query + case full + /// Matches the URL excluding the query + case ignoreQuery + /// Matches if the URL begins with the prefix + case prefix +} + +extension URL { + /// Returns the base URL string build with the scheme, host and path. "https://www.wetransfer.com/v1/test?param=test" would be "https://www.wetransfer.com/v1/test". + var baseString: String? { + guard let scheme = scheme, let host = host else { return nil } + return scheme + "://" + host + path + } + + /// Checks if this URL matches the passed URL using the provided match type. + /// + /// - Parameter url: The URL to check for a match. + /// - Parameter matchType: The approach that will be used to determine whether this URL match the provided URL. Defaults to `full`. + /// - Returns: `true` if the URL matches based on the match type; `false` otherwise. + func matches(_ otherURL: URL?, matchType: URLMatchType = .full) -> Bool { + guard let otherURL else { return false } + + switch matchType { + case .full: + return absoluteString == otherURL.absoluteString + case .ignoreQuery: + return baseString == otherURL.baseString + case .prefix: + return absoluteString.hasPrefix(otherURL.absoluteString) + } + } +} diff --git a/Tests/MockerTests/MockerTests.swift b/Tests/MockerTests/MockerTests.swift index 88db6e3..5b85356 100644 --- a/Tests/MockerTests/MockerTests.swift +++ b/Tests/MockerTests/MockerTests.swift @@ -302,10 +302,22 @@ final class MockerTests: XCTestCase { let ignoredURLQueries = URL(string: "https://www.wetransfer.com/sample-image.png?width=200&height=200")! XCTAssertTrue(MockingURLProtocol.canInit(with: URLRequest(url: ignoredURLQueries))) - Mocker.ignore(ignoredURL, ignoreQuery: true) + Mocker.ignore(ignoredURL, matchType: .ignoreQuery) XCTAssertFalse(MockingURLProtocol.canInit(with: URLRequest(url: ignoredURLQueries))) } + /// It should be possible to ignore URL prefixes and not let them be handled. + func testIgnoreURLsIgnorePrefixes() { + + let ignoredURL = URL(string: "https://www.wetransfer.com/private")! + let ignoredURLSubPath = URL(string: "https://www.wetransfer.com/private/sample-image.png")! + + XCTAssertTrue(MockingURLProtocol.canInit(with: URLRequest(url: ignoredURLSubPath))) + Mocker.ignore(ignoredURL, matchType: .prefix) + XCTAssertFalse(MockingURLProtocol.canInit(with: URLRequest(url: ignoredURLSubPath))) + XCTAssertFalse(MockingURLProtocol.canInit(with: URLRequest(url: ignoredURL))) + } + /// It should be possible to compose a url relative to a base and still have it match the full url func testComposedURLMatch() { let composedURL = URL(fileURLWithPath: "resource", relativeTo: URL(string: "https://host.com/api/"))