-
Notifications
You must be signed in to change notification settings - Fork 6
/
DDMock.swift
155 lines (135 loc) · 5.46 KB
/
DDMock.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import Foundation
public class DDMock {
private let mockDirectory = "/mockfiles"
private let jsonExtension = "json"
public static let versionString = "0.1.6"
private var mockEntries = [String: MockEntry]()
private(set) var strict: Bool = false // Enforces mocks only and no API fall-through
private(set) var addMockHeader: Bool = true // Adds a mock header to show that mock is in use
public private(set) var matchedPaths = [String]() // chronological order of paths
public var onMissingMock: (_ path: String?) -> Void = {path in
fatalError("missing stub for path: \(path ?? "<unknown>")")
}
public static let shared = DDMock()
public func initialise(strict: Bool = false, addMockHeader: Bool = true) {
self.strict = strict
self.addMockHeader = addMockHeader
let docsPath = Bundle.main.resourcePath! + mockDirectory
let fileManager = FileManager.default
fileManager.enumerator(atPath: docsPath)?.forEach({ (e) in
if let e = e as? String, let url = URL(string: e) {
if (url.pathExtension == jsonExtension) {
createMockEntry(url: url)
}
}
})
}
public func clearHistory() {
matchedPaths.removeAll()
}
private func createMockEntry(url: URL) {
let fileName = "/" + url.lastPathComponent
let key = url.path.replacingOccurrences(of: fileName, with: "")
if var entry = mockEntries[key] {
entry.files.append(url.path)
mockEntries[key] = entry
} else {
mockEntries[key] = MockEntry(path: key, files: [url.path])
}
}
private func mockEntry(for path: String, isTest: Bool) -> MockEntry? {
let entry = mockEntries[path] ?? getRegexEntry(path: path)
guard !isTest else {
return entry
}
// If strict mode is enabled, a missing entry is an error. Call handler.
if strict && entry == nil {
onMissingMock(path)
}
// Here we log the entries so that clients (like a unit test) can verify a call was made.
matchedPaths.append(path)
return entry
}
func hasMockEntry(path: String, method: String) -> EntrySetting {
switch getMockEntry(path: path, method: method, isTest: true)?.useRealAPI() {
case .none:
return .notFound
case .some(false):
return .mocked
case .some(true):
return .useRealAPI
}
}
func getMockEntry(path: String, method: String) -> MockEntry? {
return getMockEntry(path: path, method: method, isTest: false)
}
private func getMockEntry(path: String, method: String, isTest: Bool) -> MockEntry? {
guard let path = mockPath(path: path, method: method) else { return nil}
return mockEntry(for: path, isTest: isTest)
}
func hasMockEntry(request: URLRequest) -> EntrySetting {
guard let path = request.url?.path, let method = request.httpMethod else { return .notFound }
return hasMockEntry(path: path, method: method)
}
func getMockEntry(request: URLRequest) -> MockEntry? {
guard let path = request.url?.path, let method = request.httpMethod else { return nil }
return getMockEntry(path: path, method: method, isTest: false)
}
private func getRegexEntry(path: String) -> MockEntry? {
var matches = [MockEntry]()
for key in mockEntries.keys {
if (key.contains("_")) {
let regex = key.replacingRegexMatches(pattern: "_[^/]*_", replaceWith: "[^/]*")
if (path.matches(regex)) {
if let match = mockEntries[key] {
matches.append(match)
}
}
}
}
guard matches.count <= 1 else {
fatalError("Fatal Error: Multiple matches for regex entry.")
}
return matches.first
}
func getData(_ entry: MockEntry) -> Data? {
var data: Data? = nil
let f = entry.files[entry.getSelectedFile()]
do {
let docsPath = Bundle.main.resourcePath! + mockDirectory
data = try Data(contentsOf: URL(fileURLWithPath: "\(docsPath)/\(f)"), options: .mappedIfSafe)
} catch {
data = nil
}
return data
}
}
extension String {
func matches(_ regex: String) -> Bool {
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
}
func replacingRegexMatches(pattern: String, replaceWith: String = "") -> String {
var newString = ""
do {
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
let range = NSMakeRange(0, self.count)
newString = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
} catch {
debugPrint("Error \(error)")
}
return newString
}
}
extension DDMock {
func mockPath(request: URLRequest) -> String? {
if let url = request.url,
let method = request.httpMethod {
return mockPath(path: url.path, method: method)
} else {
return nil
}
}
func mockPath(path: String, method: String) -> String? {
return path.replacingRegexMatches(pattern: "^/", replaceWith: "") + "/" + method.lowercased()
}
}