-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMatchRules.java
270 lines (244 loc) · 8.97 KB
/
MatchRules.java
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
package com.easypost.easyvcr;
import com.easypost.easyvcr.internal.Utilities;
import com.easypost.easyvcr.requestelements.Request;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
/**
* Rule set for matching requests against recorded requests.
*/
public final class MatchRules {
private final List<BiFunction<Request, Request, Boolean>> rules;
/**
* Construct a new MatchRules factory.
*/
public MatchRules() {
rules = new ArrayList<>();
}
/**
* Default rule is to match on the method and URL.
*
* @return Default MatchRules object.
*/
public static MatchRules regular() {
return new MatchRules().byMethod().byFullUrl();
}
/**
* Default strict rule is to match on the method, URL and body.
*
* @return Default strict MatchRules object.
*/
public static MatchRules strict() {
return new MatchRules().byMethod().byFullUrl().byBody();
}
private void by(BiFunction<Request, Request, Boolean> rule) {
rules.add(rule);
}
/**
* Add a rule to compare the base URLs of the requests.
*
* @return This MatchRules factory.
*/
public MatchRules byBaseUrl() {
by((received, recorded) -> {
String receivedUri = getBaseUrl(received.getUri());
String recordedUri = getBaseUrl(recorded.getUri());
return receivedUri.equalsIgnoreCase(recordedUri);
});
return this;
}
/**
* Extract the base URL from a URI.
*
* @param url The URI to extract the base URL from.
* @return The base URL.
*/
private static String getBaseUrl(URI url) {
String baseUrl = url.getScheme() + "://" + url.getHost();
if (url.getPort() != -1) {
baseUrl += ":" + url.getPort();
}
if (url.getPath() != null) {
baseUrl += url.getPath();
}
return baseUrl;
}
/**
* Add a rule to compare the bodies of the requests.
*
* @param ignoredElements List of body elements to ignore when comparing the requests.
* @return This MatchRules factory.
*/
public MatchRules byBody(List<CensorElement> ignoredElements) {
by((received, recorded) -> {
String receivedBody = received.getBody();
String recordedBody = recorded.getBody();
if (receivedBody == null && recordedBody == null) {
// both have null bodies, so they match
return true;
}
if (receivedBody == null || recordedBody == null) {
// one has a null body, so they don't match
return false;
}
// remove ignored elements from the body
receivedBody = Utilities.removeJsonElements(receivedBody, ignoredElements);
recordedBody = Utilities.removeJsonElements(recordedBody, ignoredElements);
// convert body to base64string to assist comparison by removing special characters
receivedBody = Utilities.toBase64String(receivedBody);
recordedBody = Utilities.toBase64String(recordedBody);
return receivedBody.equalsIgnoreCase(recordedBody);
});
return this;
}
/**
* Add a rule to compare the bodies of the requests.
*
* @return This MatchRules factory.
*/
public MatchRules byBody() {
return byBody(null);
}
/**
* Add a rule to compare the entire requests.
* Note, this rule is very strict, and will fail if the requests are not identical (including duration).
* It is highly recommended to use the other rules to compare the requests.
*
* @return This MatchRules factory.
*/
public MatchRules byEverything() {
by((received, recorded) -> {
String receivedRequest = received.toJson();
String recordedRequest = recorded.toJson();
return receivedRequest.equalsIgnoreCase(recordedRequest);
});
return this;
}
/**
* Add a rule to compare the full URLs (including query parameters) of the requests.
* Default: query parameter order does not matter.
*
* @return This MatchRules factory.
*/
public MatchRules byFullUrl() {
return byFullUrl(false);
}
/**
* Add a rule to compare the full URLs (including query parameters) of the requests.
*
* @param exact If true, query parameters must be in the same exact order to match.
* If false, query parameter order doesn't matter.
* @return This MatchRules factory.
*/
public MatchRules byFullUrl(boolean exact) {
if (exact) {
by((received, recorded) -> {
String receivedUri = Utilities.toBase64String(received.getUriString());
String recordedUri = Utilities.toBase64String(recorded.getUriString());
return receivedUri.equalsIgnoreCase(recordedUri);
});
} else {
byBaseUrl();
by((received, recorded) -> {
Map<String, String> receivedQuery = Utilities.queryParametersToMap(received.getUri());
Map<String, String> recordedQuery = Utilities.queryParametersToMap(recorded.getUri());
if (receivedQuery.size() != recordedQuery.size()) {
return false;
}
for (Map.Entry<String, String> entry : receivedQuery.entrySet()) {
if (!recordedQuery.containsKey(entry.getKey())) {
return false;
}
}
return true;
});
}
return this;
}
/**
* Add a rule to compare a specific header of the requests.
*
* @param name Key of the header to compare.
* @return This MatchRules factory.
*/
public MatchRules byHeader(String name) {
by((received, recorded) -> {
Map<String, List<String>> receivedHeaders = received.getHeaders();
Map<String, List<String>> recordedHeaders = recorded.getHeaders();
if (!receivedHeaders.containsKey(name) || !recordedHeaders.containsKey(name)) {
return false;
}
List<String> receivedHeader = receivedHeaders.get(name);
List<String> recordedHeader = recordedHeaders.get(name);
return receivedHeader.equals(recordedHeader);
});
return this;
}
/**
* Add a rule to compare the headers of the requests.
* Default: headers strictness is set to false.
*
* @return This MatchRules factory.
*/
public MatchRules byHeaders() {
return byHeaders(false);
}
/**
* Add a rule to compare the headers of the requests.
*
* @param exact If true, both requests must have the exact same headers.
* If false, as long as the evaluated request has all the headers of the matching request
* (and potentially more), the match is considered valid.
* @return This MatchRules factory.
*/
public MatchRules byHeaders(boolean exact) {
if (exact) {
// first, we'll check that there are the same number of headers in both requests.
// If they are, then the second check is guaranteed to compare all headers.
by((received, recorded) -> received.getHeaders().size() == recorded.getHeaders().size());
}
by((received, recorded) -> {
Map<String, List<String>> receivedHeaders = received.getHeaders();
Map<String, List<String>> recordedHeaders = recorded.getHeaders();
for (String headerName : receivedHeaders.keySet()) {
if (!recordedHeaders.containsKey(headerName)) {
return false;
}
if (!receivedHeaders.get(headerName).equals(recordedHeaders.get(headerName))) {
return false;
}
}
return true;
});
return this;
}
/**
* Add a rule to compare the HTTP methods of the requests.
*
* @return This MatchRules factory.
*/
public MatchRules byMethod() {
by((received, recorded) -> received.getMethod().equalsIgnoreCase(recorded.getMethod()));
return this;
}
/**
* Execute rules to determine if the received request matches the recorded request.
*
* @param receivedRequest Request to find a match for.
* @param recordedRequest Request to compare against.
* @return True if the received request matches the recorded request, false otherwise.
*/
public boolean requestsMatch(Request receivedRequest, Request recordedRequest) {
if (rules.size() == 0) {
return true;
}
for (BiFunction<Request, Request, Boolean> rule : rules) {
if (!rule.apply(receivedRequest, recordedRequest)) {
return false;
}
}
return true;
}
}