requests = new ArrayList<>();
39 |
40 | private boolean debuggingOn;
41 |
42 | /**
43 | * Creates mock of HttpClient
44 | */
45 | public HttpClientMock() {
46 | this("");
47 | }
48 |
49 | /**
50 | * Creates mock of HttpClient with default host. All defined conditions without host will use default host
51 | *
52 | * @param host default host for later conditions
53 | */
54 | public HttpClientMock(String host) {
55 | this.host = host;
56 | this.debugger = new Debugger();
57 | }
58 |
59 | /**
60 | * Creates mock of HttpClient with default host. All defined conditions without host will use default host
61 | *
62 | * @param host default host for later conditions
63 | * @param debugger debugger used for testing
64 | */
65 | HttpClientMock(String host, Debugger debugger) {
66 | this.host = host;
67 | this.debugger = debugger;
68 | }
69 |
70 | /**
71 | * Resets mock to initial state where there are no rules and no previous requests.
72 | */
73 | public void reset() {
74 | this.rulesUnderConstruction.clear();
75 | this.requests.clear();
76 | }
77 |
78 | /**
79 | * Creates verification builder.
80 | *
81 | * @return request number verification builder
82 | */
83 | public HttpClientVerify verify() {
84 | return new HttpClientVerify(host, requests);
85 | }
86 |
87 | /**
88 | * Starts defining new rule which requires HTTP POST method.
89 | *
90 | * @return HttpClientMockBuilder which allows to define new rule
91 | */
92 | public HttpClientMockBuilder onPost() {
93 | return newRule(POST);
94 | }
95 |
96 | /**
97 | * Starts defining new rule which requires HTTP GET method.
98 | *
99 | * @return HttpClientMockBuilder which allows to define new rule
100 | */
101 | public HttpClientMockBuilder onGet() {
102 | return newRule(GET);
103 | }
104 |
105 | /**
106 | * Starts defining new rule which requires HTTP DELETE method.
107 | *
108 | * @return HttpClientMockBuilder which allows to define new rule
109 | */
110 | public HttpClientMockBuilder onDelete() {
111 | return newRule(DELETE);
112 | }
113 |
114 | /**
115 | * Starts defining new rule which requires HTTP HEAD method.
116 | *
117 | * @return HttpClientMockBuilder which allows to define new rule
118 | */
119 | public HttpClientMockBuilder onHead() {
120 | return newRule(HEAD);
121 | }
122 |
123 | /**
124 | * Starts defining new rule which requires HTTP OPTION method.
125 | *
126 | * @return HttpClientMockBuilder which allows to define new rule
127 | */
128 | public HttpClientMockBuilder onOptions() {
129 | return newRule(OPTIONS);
130 | }
131 |
132 | /**
133 | * Starts defining new rule which requires HTTP PUT method.
134 | *
135 | * @return HttpClientMockBuilder which allows to define new rule
136 | */
137 | public HttpClientMockBuilder onPut() {
138 | return newRule(PUT);
139 | }
140 |
141 | /**
142 | * Starts defining new rule which requires HTTP PATCH method.
143 | *
144 | * @return HttpClientMockBuilder which allows to define new rule
145 | */
146 | public HttpClientMockBuilder onPatch() {
147 | return newRule(PATCH);
148 | }
149 |
150 | /**
151 | * Starts defining new rule which requires HTTP GET method and url. If provided url starts with "/" request url must be equal to concatenation of default
152 | * host and url. Otherwise request url must equal to provided url. If provided url contains query parameters and/or reference they are parsed and added as a
153 | * separate conditions. For example:
httpClientMock.onGet("http://localhost/login?user=Ben#edit");
is equal to
154 | * httpClientMock.onGet("http://localhost/login").withParameter("user","Ben").withReference("edit);
155 | *
156 | * @param url required url
157 | * @return HttpClientMockBuilder which allows to define new rule
158 | */
159 | public HttpClientMockBuilder onGet(String url) {
160 | Objects.requireNonNull(url, "URL must be not null");
161 | return newRule(GET, url);
162 | }
163 |
164 | /**
165 | * Starts defining new rule which requires HTTP POST method and url. URL works the same way as in {@link #onGet(String) onGet}
166 | *
167 | * @param url required url
168 | * @return HttpClientMockBuilder which allows to define new rule
169 | */
170 | public HttpClientMockBuilder onPost(String url) {
171 | Objects.requireNonNull(url, "URL must be not null");
172 | return newRule(POST, url);
173 | }
174 |
175 | /**
176 | * Starts defining new rule which requires HTTP PUT method and url. URL works the same way as in {@link #onGet(String) onGet}
177 | *
178 | * @param url required url
179 | * @return HttpClientMockBuilder which allows to define new rule
180 | */
181 | public HttpClientMockBuilder onPut(String url) {
182 | Objects.requireNonNull(url, "URL must be not null");
183 | return newRule(PUT, url);
184 | }
185 |
186 | /**
187 | * Starts defining new rule which requires HTTP DELETE method and url. URL works the same way as in {@link #onGet(String) onGet}
188 | *
189 | * @param url required url
190 | * @return HttpClientMockBuilder which allows to define new rule
191 | */
192 | public HttpClientMockBuilder onDelete(String url) {
193 | Objects.requireNonNull(url, "URL must be not null");
194 | return newRule(DELETE, url);
195 | }
196 |
197 | /**
198 | * Starts defining new rule which requires HTTP HEAD method and url. URL works the same way as in {@link #onGet(String) onGet}
199 | *
200 | * @param url required url
201 | * @return HttpClientMockBuilder which allows to define new rule
202 | */
203 | public HttpClientMockBuilder onHead(String url) {
204 | Objects.requireNonNull(url, "URL must be not null");
205 | return newRule(HEAD, url);
206 | }
207 |
208 | /**
209 | * Starts defining new rule which requires HTTP OPTIONS method and url. URL works the same way as in {@link #onGet(String) onGet}
210 | *
211 | * @param url required url
212 | * @return HttpClientMockBuilder which allows to define new rule
213 | */
214 | public HttpClientMockBuilder onOptions(String url) {
215 | Objects.requireNonNull(url, "URL must be not null");
216 | return newRule(OPTIONS, url);
217 | }
218 |
219 | /**
220 | * Starts defining new rule which requires HTTP PATCH method and url. URL works the same way as in {@link #onGet(String) onGet}
221 | *
222 | * @param url required url
223 | * @return HttpClientMockBuilder which allows to define new rule
224 | */
225 | public HttpClientMockBuilder onPatch(String url) {
226 | Objects.requireNonNull(url, "URL must be not null");
227 | return newRule(PATCH, url);
228 | }
229 |
230 | private HttpClientMockBuilder newRule(String method) {
231 | RuleBuilder r = new RuleBuilder(method);
232 | rulesUnderConstruction.add(r);
233 | return new HttpClientMockBuilder(r);
234 | }
235 |
236 | private HttpClientMockBuilder newRule(String method, String url) {
237 | RuleBuilder r = new RuleBuilder(method, host, url);
238 | rulesUnderConstruction.add(r);
239 | return new HttpClientMockBuilder(r);
240 | }
241 |
242 | @Override
243 | public Optional cookieHandler() {
244 | return Optional.empty();
245 | }
246 |
247 | @Override
248 | public Optional connectTimeout() {
249 | return Optional.empty();
250 | }
251 |
252 | @Override
253 | public Redirect followRedirects() {
254 | return null;
255 | }
256 |
257 | @Override
258 | public Optional proxy() {
259 | return Optional.empty();
260 | }
261 |
262 | @Override
263 | public SSLContext sslContext() {
264 | return null;
265 | }
266 |
267 | @Override
268 | public SSLParameters sslParameters() {
269 | return null;
270 | }
271 |
272 | @Override
273 | public Optional authenticator() {
274 | return Optional.empty();
275 | }
276 |
277 | @Override
278 | public Version version() {
279 | return null;
280 | }
281 |
282 | @Override
283 | public Optional executor() {
284 | return Optional.empty();
285 | }
286 |
287 | @Override
288 | public HttpResponse send(HttpRequest request, HttpResponse.BodyHandler responseBodyHandler) throws IOException {
289 | Objects.requireNonNull(request,"request must be not null");
290 | Objects.requireNonNull(responseBodyHandler,"responseBodyHandler must be not null");
291 | var rule = findNextRule(request);
292 | var serverResponse = rule.produceResponse();
293 | var body = submitToBodyHandler(serverResponse, responseBodyHandler);
294 | var httpHeaders = HttpHeaders.of(serverResponse.headers(), (a, b) -> true);
295 | return new HttpResponseProxy<>(serverResponse.statusCode(), httpHeaders, body, request);
296 | }
297 |
298 | private Rule findNextRule(HttpRequest request) {
299 | synchronized (rulesUnderConstruction) {
300 | rules.addAll(
301 | rulesUnderConstruction.stream()
302 | .map(RuleBuilder::build)
303 | .collect(Collectors.toList())
304 | );
305 | rulesUnderConstruction.clear();
306 | }
307 |
308 | requests.add(request);
309 |
310 | final Optional rule = rules.stream()
311 | .filter(r -> r.matches(request))
312 | .reduce((a, b) -> b);
313 |
314 | if (debuggingOn || rule.isEmpty()) {
315 | debugger.debug(rules, request);
316 | }
317 |
318 | return rule.orElseThrow(() -> new NoMatchingRuleException(request));
319 | }
320 |
321 | @Override
322 | public CompletableFuture> sendAsync(HttpRequest request, HttpResponse.BodyHandler responseBodyHandler) {
323 | Objects.requireNonNull(request,"request must be not null");
324 | Objects.requireNonNull(responseBodyHandler,"responseBodyHandler must be not null");
325 | try {
326 | var rule = findNextRule(request);
327 | var serverResponse = rule.produceResponse();
328 | var body = submitToBodyHandler(serverResponse, responseBodyHandler);
329 | var httpHeaders = HttpHeaders.of(serverResponse.headers(), (a, b) -> true);
330 | var response = new HttpResponseProxy<>(serverResponse.statusCode(), httpHeaders, body, request);
331 | return CompletableFuture.completedFuture(response);
332 | } catch (IOException e) {
333 | throw new IllegalStateException(e);
334 | }
335 |
336 | }
337 |
338 | @Override
339 | public CompletableFuture> sendAsync(HttpRequest request, HttpResponse.BodyHandler responseBodyHandler, HttpResponse.PushPromiseHandler pushPromiseHandler) {
340 | return sendAsync(request, responseBodyHandler);
341 | }
342 |
343 | public void debugOn() {
344 | debuggingOn = true;
345 | }
346 |
347 | public void debugOff() {
348 | debuggingOn = false;
349 | }
350 |
351 | private T submitToBodyHandler(MockedServerResponse serverResponse, HttpResponse.BodyHandler responseBodyHandler) {
352 | var bodyBytes = serverResponse.getBodyBytes();
353 | var subscriber = responseBodyHandler.apply(produceResponseInfo(serverResponse));
354 | var publisher = new SubmissionPublisher>();
355 | publisher.subscribe(subscriber);
356 | if (bodyBytes.array().length != 0) {
357 | publisher.submit(List.of(bodyBytes));
358 | }
359 | publisher.close();
360 | try {
361 | return subscriber.getBody().toCompletableFuture().get();
362 | } catch (InterruptedException | ExecutionException e) {
363 | throw new IllegalStateException("Error reading the mocked response body - did you forget to provide it? " +
364 | "If there should be no body, try using BodyHandlers.discarding() when making the request", e);
365 | }
366 | }
367 |
368 | private HttpResponse.ResponseInfo produceResponseInfo(MockedServerResponse response) {
369 | return new HttpResponse.ResponseInfo() {
370 | @Override
371 | public int statusCode() {
372 | return response.statusCode();
373 | }
374 |
375 | @Override
376 | public HttpHeaders headers() {
377 | return HttpHeaders.of(response.headers(), (a, b) -> true);
378 | }
379 |
380 | @Override
381 | public Version version() {
382 | return Version.HTTP_1_1;
383 | }
384 | };
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/HttpClientMockBuilder.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import com.pgssoft.httpclient.internal.condition.BodyCondition;
4 | import com.pgssoft.httpclient.internal.condition.HeaderCondition;
5 | import com.pgssoft.httpclient.internal.rule.RuleBuilder;
6 | import org.hamcrest.Matcher;
7 | import org.hamcrest.Matchers;
8 |
9 | import java.io.IOException;
10 | import java.nio.charset.Charset;
11 | import java.util.Objects;
12 |
13 | public final class HttpClientMockBuilder {
14 |
15 | private final RuleBuilder ruleBuilder;
16 | private final HttpClientResponseBuilder responseBuilder;
17 |
18 | HttpClientMockBuilder(RuleBuilder ruleBuilder) {
19 | this.ruleBuilder = ruleBuilder;
20 | this.responseBuilder = new HttpClientResponseBuilder(ruleBuilder);
21 | }
22 |
23 | /**
24 | * Adds header condition. Header must be equal to provided value.
25 | *
26 | * @param header header name
27 | * @param value expected value
28 | * @return condition builder
29 | */
30 | public HttpClientMockBuilder withHeader(String header, String value) {
31 | Objects.requireNonNull(header, "header must be not null");
32 | return withHeader(header, Matchers.equalTo(value));
33 | }
34 |
35 | /**
36 | * Adds header condition. Header must be equal to provided value.
37 | *
38 | * @param header header name
39 | * @param matcher header value matcher
40 | * @return condition builder
41 | */
42 | public HttpClientMockBuilder withHeader(String header, Matcher matcher) {
43 | Objects.requireNonNull(header, "header must be not null");
44 | Objects.requireNonNull(matcher, "matcher must be not null");
45 | ruleBuilder.addCondition(new HeaderCondition(header, matcher));
46 | return this;
47 | }
48 |
49 | /**
50 | * Adds reference condition. Reference must be equal to provided value.
51 | *
52 | * @param reference expected reference
53 | * @return conditions builder
54 | */
55 | public HttpClientMockBuilder withReference(String reference) {
56 | Objects.requireNonNull(reference, "reference must be not null");
57 | return withReference(Matchers.equalTo(reference));
58 | }
59 |
60 | /**
61 | * Adds reference condition. Reference must match.
62 | *
63 | * @param matcher reference matcher
64 | * @return conditions builder
65 | */
66 | public HttpClientMockBuilder withReference(Matcher matcher) {
67 | Objects.requireNonNull(matcher, "matcher must be not null");
68 | ruleBuilder.setReferenceCondition(matcher);
69 | return this;
70 | }
71 |
72 | /**
73 | * Adds parameter condition. Parameter must be equal to provided value.
74 | *
75 | * @param name parameter name
76 | * @param value expected parameter value
77 | * @return condition builder
78 | */
79 | public HttpClientMockBuilder withParameter(String name, String value) {
80 | Objects.requireNonNull(name, "name must be not null");
81 | Objects.requireNonNull(value, "value must be not null");
82 | return withParameter(name, Matchers.equalTo(value));
83 | }
84 |
85 | /**
86 | * Adds parameter condition. Parameter value must match.
87 | *
88 | * @param name parameter name
89 | * @param matcher parameter value matcher
90 | * @return condition builder
91 | */
92 | public HttpClientMockBuilder withParameter(String name, Matcher matcher) {
93 | Objects.requireNonNull(name, "name must be not null");
94 | Objects.requireNonNull(matcher, "matcher must be not null");
95 | ruleBuilder.setParameterCondition(name, matcher);
96 | return this;
97 | }
98 |
99 | /**
100 | * Adds custom conditions.
101 | *
102 | * @param condition custom condition
103 | * @return condition builder
104 | */
105 | public HttpClientMockBuilder with(Condition condition) {
106 | Objects.requireNonNull(condition, "condition must be not null");
107 | ruleBuilder.addCondition(condition);
108 | return this;
109 | }
110 |
111 | /**
112 | * Adds body condition. Request body must match provided matcher.
113 | *
114 | * @param matcher custom condition
115 | * @return condition builder
116 | */
117 | public HttpClientMockBuilder withBody(Matcher matcher) {
118 | Objects.requireNonNull(matcher, "matcher must be not null");
119 | ruleBuilder.addCondition(new BodyCondition(matcher));
120 | return this;
121 | }
122 |
123 | /**
124 | * Adds host condition. Request host must be equal to provided value.
125 | *
126 | * @param host expected host
127 | * @return condition builder
128 | */
129 | public HttpClientMockBuilder withHost(String host) {
130 | Objects.requireNonNull(host, "host must be not null");
131 | ruleBuilder.addHostCondition(host);
132 | return this;
133 | }
134 |
135 | /**
136 | * Adds path condition. Request path must be equal to provided value.
137 | *
138 | * @param path expected path
139 | * @return condition builder
140 | */
141 | public HttpClientMockBuilder withPath(String path) {
142 | Objects.requireNonNull(path, "path must be not null");
143 | return withPath(Matchers.equalTo(path));
144 | }
145 |
146 | /**
147 | * Adds path condition. Request path must match.
148 | *
149 | * @param matcher path matcher
150 | * @return condition builder
151 | */
152 | public HttpClientMockBuilder withPath(Matcher matcher) {
153 | Objects.requireNonNull(matcher, "matcher must be not null");
154 | ruleBuilder.setPathCondition(matcher);
155 | return this;
156 | }
157 |
158 | /**
159 | * Adds custom action.
160 | *
161 | * @param action custom action
162 | * @return response builder
163 | */
164 | public HttpClientResponseBuilder doAction(Action action) {
165 | Objects.requireNonNull(action, "action must be not null");
166 | return responseBuilder.doAction(action);
167 | }
168 |
169 | /**
170 | * Adds action which returns provided response in UTF-8 and status 200.
171 | *
172 | * @param response response to return
173 | * @return response builder
174 | */
175 | public HttpClientResponseBuilder doReturn(String response) {
176 | Objects.requireNonNull(response, "response must be not null");
177 | return responseBuilder.doReturn(response);
178 | }
179 |
180 | /**
181 | * Adds action which returns provided response and status in UTF-8.
182 | *
183 | * @param statusCode status to return
184 | * @param response response to return
185 | * @return response builder
186 | */
187 | public HttpClientResponseBuilder doReturn(int statusCode, String response) {
188 | Objects.requireNonNull(response, "response must be not null");
189 | return responseBuilder.doReturn(statusCode, response);
190 | }
191 |
192 | /**
193 | * Adds action which returns provided response in provided charset and status 200.
194 | *
195 | * @param response response to return
196 | * @param charset charset to return
197 | * @return response builder
198 | */
199 | public HttpClientResponseBuilder doReturn(String response, Charset charset) {
200 | Objects.requireNonNull(response, "response must be not null");
201 | Objects.requireNonNull(charset, "charset must be not null");
202 | return responseBuilder.doReturn(response, charset);
203 | }
204 |
205 | /**
206 | * Adds action which returns empty message and provided status.
207 | *
208 | * @param statusCode status to return
209 | * @return response builder
210 | */
211 | public HttpClientResponseBuilder doReturnStatus(int statusCode) {
212 | return responseBuilder.doReturnStatus(statusCode);
213 | }
214 |
215 | /**
216 | * Adds action which throws provided exception.
217 | *
218 | * @param exception exception to be thrown
219 | * @return response builder
220 | */
221 | public HttpClientResponseBuilder doThrowException(IOException exception) {
222 | Objects.requireNonNull(exception, "exception must be not null");
223 | return responseBuilder.doThrowException(exception);
224 | }
225 |
226 | /**
227 | * Adds action which returns provided JSON in UTF-8 and status 200. Additionally it sets "Content-type" header to "application/json".
228 | *
229 | * @param response JSON to return
230 | * @return response builder
231 | */
232 | public HttpClientResponseBuilder doReturnJSON(String response) {
233 | Objects.requireNonNull(response, "response must be not null");
234 | return responseBuilder.doReturnJSON(response);
235 | }
236 |
237 | /**
238 | * Adds action which returns provided JSON in provided encoding and status 200. Additionally it sets "Content-type" header to "application/json".
239 | *
240 | * @param response JSON to return
241 | * @return response builder
242 | */
243 | public HttpClientResponseBuilder doReturnJSON(String response, Charset charset) {
244 | Objects.requireNonNull(response, "response must be not null");
245 | Objects.requireNonNull(charset, "charset must be not null");
246 | return responseBuilder.doReturnJSON(response, charset);
247 | }
248 |
249 | /**
250 | * Adds action which returns provided XML in UTF-8 and status 200. Additionally it sets "Content-type" header to "application/xml".
251 | *
252 | * @param response JSON to return
253 | * @return response builder
254 | */
255 | public HttpClientResponseBuilder doReturnXML(String response) {
256 | Objects.requireNonNull(response, "response must be not null");
257 | return responseBuilder.doReturnXML(response);
258 | }
259 |
260 | /**
261 | * Adds action which returns provided XML in UTF-8 and status 200. Additionally it sets "Content-type" header to "application/xml".
262 | *
263 | * @param response JSON to return
264 | * @return response builder
265 | */
266 | public HttpClientResponseBuilder doReturnXML(String response, Charset charset) {
267 | Objects.requireNonNull(response, "response must be not null");
268 | Objects.requireNonNull(charset, "charset must be not null");
269 | return responseBuilder.doReturnXML(response, charset);
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/HttpClientResponseBuilder.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import com.pgssoft.httpclient.internal.action.*;
4 | import com.pgssoft.httpclient.internal.rule.RuleBuilder;
5 |
6 | import java.io.IOException;
7 | import java.nio.charset.Charset;
8 | import java.nio.charset.StandardCharsets;
9 |
10 | public final class HttpClientResponseBuilder {
11 |
12 | private static final String APPLICATION_JSON = "application/json";
13 | private static final String APPLICATION_XML = "application/xml";
14 |
15 | private final RuleBuilder ruleBuilder;
16 |
17 | HttpClientResponseBuilder(RuleBuilder ruleBuilder) {
18 | this.ruleBuilder = ruleBuilder;
19 | }
20 |
21 | public HttpClientResponseBuilder withHeader(String name, String value) {
22 | return doAction(new SetHeaderAction(name, value));
23 | }
24 |
25 | public HttpClientResponseBuilder withStatus(int statusCode) {
26 | return doAction(new SetStatusAction(statusCode));
27 | }
28 |
29 | public HttpClientResponseBuilder doAction(Action action) {
30 | ruleBuilder.addAction(action);
31 | return this;
32 | }
33 |
34 | public HttpClientResponseBuilder doReturn(String response) {
35 | return doReturn(response, StandardCharsets.UTF_8);
36 | }
37 |
38 | public HttpClientResponseBuilder doReturn(int statusCode, String response) {
39 | return doReturn(statusCode, response, StandardCharsets.UTF_8);
40 | }
41 |
42 | public HttpClientResponseBuilder doReturn(String response, Charset charset) {
43 | return doReturn(200, response, charset);
44 | }
45 |
46 | public HttpClientResponseBuilder doReturn(int statusCode, String response, Charset charset) {
47 | ruleBuilder.addActionBundle(new SetBodyStringAction(response, charset));
48 | ruleBuilder.addAction(new SetStatusAction(statusCode));
49 | return new HttpClientResponseBuilder(ruleBuilder);
50 | }
51 |
52 | public HttpClientResponseBuilder doReturnStatus(int statusCode) {
53 | ruleBuilder.addActionBundle(new SetStatusAction(statusCode));
54 | return new HttpClientResponseBuilder(ruleBuilder);
55 | }
56 |
57 | public HttpClientResponseBuilder doThrowException(IOException exception) {
58 | ruleBuilder.addActionBundle(new ThrowExceptionAction(exception));
59 | return new HttpClientResponseBuilder(ruleBuilder);
60 | }
61 |
62 | public HttpClientResponseBuilder doReturnJSON(String response) {
63 | return doReturnJSON(response, StandardCharsets.UTF_8);
64 | }
65 |
66 | public HttpClientResponseBuilder doReturnJSON(String response, Charset charset) {
67 | return doReturn(response, charset).withHeader("Content-type", buildContentTypeHeader(APPLICATION_JSON,charset));
68 | }
69 |
70 |
71 | public HttpClientResponseBuilder doReturnXML(String response) {
72 | return doReturnXML(response, StandardCharsets.UTF_8);
73 | }
74 |
75 | public HttpClientResponseBuilder doReturnXML(String response, Charset charset) {
76 | return doReturn(response, charset).withHeader("Content-type", buildContentTypeHeader(APPLICATION_XML,charset));
77 | }
78 |
79 | private String buildContentTypeHeader(String type, Charset charset) {
80 | return String.format("%s; charset=%s",type,charset.name());
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/HttpClientVerify.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import com.pgssoft.httpclient.internal.rule.RuleBuilder;
4 |
5 | import java.net.http.HttpRequest;
6 | import java.util.List;
7 | import java.util.Objects;
8 |
9 | import static com.pgssoft.httpclient.internal.HttpMethods.*;
10 |
11 | public final class HttpClientVerify {
12 |
13 | private final String defaultHost;
14 | private final List requests;
15 |
16 | HttpClientVerify(String defaultHost, List requests) {
17 | this.requests = requests;
18 | this.defaultHost = defaultHost;
19 | }
20 |
21 | private HttpClientVerifyBuilder newRule(String method) {
22 | RuleBuilder r = new RuleBuilder(method);
23 | return new HttpClientVerifyBuilder(r, requests);
24 | }
25 |
26 | private HttpClientVerifyBuilder newRule(String method, String url) {
27 | RuleBuilder r = new RuleBuilder(method, defaultHost, url);
28 | return new HttpClientVerifyBuilder(r, requests);
29 | }
30 |
31 | public HttpClientVerifyBuilder post(String url) {
32 | Objects.requireNonNull(url, "URL must be not null");
33 | return newRule(POST, url);
34 | }
35 |
36 | public HttpClientVerifyBuilder get(String url) {
37 | Objects.requireNonNull(url, "URL must be not null");
38 | return newRule(GET, url);
39 | }
40 |
41 | public HttpClientVerifyBuilder put(String url) {
42 | Objects.requireNonNull(url, "URL must be not null");
43 | return newRule(PUT, url);
44 | }
45 |
46 | public HttpClientVerifyBuilder delete(String url) {
47 | Objects.requireNonNull(url, "URL must be not null");
48 | return newRule(DELETE, url);
49 | }
50 |
51 | public HttpClientVerifyBuilder head(String url) {
52 | Objects.requireNonNull(url, "URL must be not null");
53 | return newRule(HEAD, url);
54 | }
55 |
56 | public HttpClientVerifyBuilder options(String url) {
57 | Objects.requireNonNull(url, "URL must be not null");
58 | return newRule(OPTIONS, url);
59 | }
60 |
61 | public HttpClientVerifyBuilder patch(String url) {
62 | Objects.requireNonNull(url, "URL must be not null");
63 | return newRule(PATCH, url);
64 | }
65 |
66 | public HttpClientVerifyBuilder post() {
67 | return newRule(POST);
68 | }
69 |
70 | public HttpClientVerifyBuilder get() {
71 | return newRule(GET);
72 | }
73 |
74 | public HttpClientVerifyBuilder put() {
75 | return newRule(PUT);
76 | }
77 |
78 | public HttpClientVerifyBuilder delete() {
79 | return newRule(DELETE);
80 | }
81 |
82 | public HttpClientVerifyBuilder head() {
83 | return newRule(HEAD);
84 | }
85 |
86 | public HttpClientVerifyBuilder options() {
87 | return newRule(OPTIONS);
88 | }
89 |
90 | public HttpClientVerifyBuilder patch() {
91 | return newRule(PATCH);
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/HttpClientVerifyBuilder.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import com.pgssoft.httpclient.internal.condition.BodyCondition;
4 | import com.pgssoft.httpclient.internal.condition.HeaderCondition;
5 | import com.pgssoft.httpclient.internal.rule.Rule;
6 | import com.pgssoft.httpclient.internal.rule.RuleBuilder;
7 | import org.hamcrest.Matcher;
8 |
9 | import java.net.http.HttpRequest;
10 | import java.util.List;
11 | import java.util.Objects;
12 |
13 | import static org.hamcrest.Matchers.equalTo;
14 |
15 | public final class HttpClientVerifyBuilder {
16 |
17 | private final RuleBuilder ruleBuilder;
18 | private final List requests;
19 |
20 | HttpClientVerifyBuilder(RuleBuilder ruleBuilder, List requests) {
21 | this.requests = requests;
22 | this.ruleBuilder = ruleBuilder;
23 | }
24 |
25 | /**
26 | * Adds header condition. Header must be equal to provided value.
27 | *
28 | * @param header header name
29 | * @param value expected value
30 | * @return verification builder
31 | */
32 | public HttpClientVerifyBuilder withHeader(String header, String value) {
33 | Objects.requireNonNull(header, "header must be not null");
34 | return withHeader(header, equalTo(value));
35 | }
36 |
37 | /**
38 | * Adds header condition. Header must be equal to provided value.
39 | *
40 | * @param header header name
41 | * @param matcher header value matcher
42 | * @return verification builder
43 | */
44 | public HttpClientVerifyBuilder withHeader(String header, Matcher matcher) {
45 | Objects.requireNonNull(header, "header must be not null");
46 | Objects.requireNonNull(matcher, "matcher must be not null");
47 | ruleBuilder.addCondition(new HeaderCondition(header, matcher));
48 | return this;
49 | }
50 |
51 | /**
52 | * Adds reference condition. Reference must be equal to provided value.
53 | *
54 | * @param reference expected reference
55 | * @return conditions builder
56 | */
57 | public HttpClientVerifyBuilder withReference(String reference) {
58 | Objects.requireNonNull(reference, "reference must be not null");
59 | return withReference(equalTo(reference));
60 | }
61 |
62 | /**
63 | * Adds reference condition. Reference must match.
64 | *
65 | * @param matcher reference matcher
66 | * @return conditions builder
67 | */
68 | public HttpClientVerifyBuilder withReference(Matcher matcher) {
69 | Objects.requireNonNull(matcher, "matcher must be not null");
70 | ruleBuilder.setReferenceCondition(matcher);
71 | return this;
72 | }
73 |
74 | /**
75 | * Adds parameter condition. Parameter must be equal to provided value.
76 | *
77 | * @param name parameter name
78 | * @param value expected parameter value
79 | * @return verification builder
80 | */
81 | public HttpClientVerifyBuilder withParameter(String name, String value) {
82 | Objects.requireNonNull(name, "name must be not null");
83 | Objects.requireNonNull(value, "value must be not null");
84 | return withParameter(name, equalTo(value));
85 | }
86 |
87 | /**
88 | * Adds parameter condition. Parameter value must match.
89 | *
90 | * @param name parameter name
91 | * @param matcher parameter value matcher
92 | * @return verification builder
93 | */
94 | public HttpClientVerifyBuilder withParameter(String name, Matcher matcher) {
95 | Objects.requireNonNull(name, "name must be not null");
96 | Objects.requireNonNull(matcher, "matcher must be not null");
97 | ruleBuilder.setParameterCondition(name, matcher);
98 | return this;
99 | }
100 |
101 | /**
102 | * Adds custom conditions.
103 | *
104 | * @param condition custom condition
105 | * @return verification builder
106 | */
107 | public HttpClientVerifyBuilder with(Condition condition) {
108 | Objects.requireNonNull(condition, "condition must be not null");
109 | ruleBuilder.addCondition(condition);
110 | return this;
111 | }
112 |
113 | /**
114 | * Adds body condition. Request body must match provided matcher.
115 | *
116 | * @param matcher custom condition
117 | * @return verification builder
118 | */
119 | public HttpClientVerifyBuilder withBody(Matcher matcher) {
120 | Objects.requireNonNull(matcher, "matcher must be not null");
121 | ruleBuilder.addCondition(new BodyCondition(matcher));
122 | return this;
123 | }
124 |
125 | /**
126 | * Adds host condition. Request host must be equal to provided value.
127 | *
128 | * @param host expected host
129 | * @return verification builder
130 | */
131 | public HttpClientVerifyBuilder withHost(String host) {
132 | Objects.requireNonNull(host, "host must be not null");
133 | ruleBuilder.addHostCondition(host);
134 | return this;
135 | }
136 |
137 | /**
138 | * Adds path condition. Request path must be equal to provided value.
139 | *
140 | * @param path expected path
141 | * @return verification builder
142 | */
143 | public HttpClientVerifyBuilder withPath(String path) {
144 | Objects.requireNonNull(path, "path must be not null");
145 | return withPath(equalTo(path));
146 | }
147 |
148 | /**
149 | * Adds path condition. Request path must match.
150 | *
151 | * @param matcher path matcher
152 | * @return verification builder
153 | */
154 | public HttpClientVerifyBuilder withPath(Matcher matcher) {
155 | Objects.requireNonNull(matcher, "matcher must be not null");
156 | ruleBuilder.setPathCondition(matcher);
157 | return this;
158 | }
159 |
160 | /**
161 | * Verifies if there were no request matching defined conditions.
162 | */
163 | public void notCalled() {
164 | called(0);
165 | }
166 |
167 | /**
168 | * Verifies if there was exactly one request matching defined conditions.
169 | */
170 | public void called() {
171 | called(1);
172 | }
173 |
174 | /**
175 | * Verifies number of request matching defined conditions.
176 | *
177 | * @param numberOfCalls expected number of calls
178 | */
179 | public void called(int numberOfCalls) {
180 | called(equalTo(numberOfCalls));
181 | }
182 |
183 | /**
184 | * Verifies number of request matching defined conditions.
185 | *
186 | * @param numberOfCalls expected number of calls
187 | */
188 | public void called(Matcher numberOfCalls) {
189 | Rule rule = ruleBuilder.build();
190 | int matchingCalls = (int)requests.stream()
191 | .filter(rule::matches)
192 | .count();
193 | if (!numberOfCalls.matches(matchingCalls)) {
194 | throw new IllegalStateException(String.format("Expected %s calls, but found %s.", numberOfCalls, matchingCalls));
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/MockedServerResponse.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.*;
5 |
6 | public class MockedServerResponse {
7 |
8 | private final int statusCode;
9 | private final Map> headers;
10 | private final ByteBuffer bodyBytes;
11 |
12 | private MockedServerResponse(int statusCode, Map> headers, ByteBuffer bodyBytes) {
13 | this.statusCode = statusCode;
14 | this.headers = headers;
15 | this.bodyBytes = bodyBytes;
16 | }
17 |
18 | public int statusCode() {
19 | return statusCode;
20 | }
21 |
22 | public Map> headers() {
23 | return headers;
24 | }
25 |
26 | public ByteBuffer getBodyBytes() {
27 | return bodyBytes;
28 | }
29 |
30 |
31 | public final static class Builder {
32 |
33 | private int statusCode;
34 | private final Map> headers = new HashMap<>();
35 | private ByteBuffer bodyBytes = ByteBuffer.wrap(new byte[]{});
36 |
37 | public void setStatusCode(int statusCode) {
38 | this.statusCode = statusCode;
39 | }
40 |
41 | public void addHeader(String key, String value) {
42 | headers.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
43 | }
44 |
45 | public void setBodyBytes(ByteBuffer bodyBytes) {
46 | this.bodyBytes = bodyBytes;
47 | }
48 |
49 | public MockedServerResponse build() {
50 | return new MockedServerResponse(statusCode, headers, bodyBytes);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/NoMatchingRuleException.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import java.net.http.HttpRequest;
4 |
5 | class NoMatchingRuleException extends IllegalStateException {
6 | NoMatchingRuleException(HttpRequest request) {
7 | super("No rule found for request: [" + request.method() + ": " + request.uri() + "]");
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/HttpMethods.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | public class HttpMethods {
4 | public static final String POST = "POST";
5 | public static final String GET = "GET";
6 | public static final String DELETE = "DELETE";
7 | public static final String HEAD = "HEAD";
8 | public static final String OPTIONS = "OPTIONS";
9 | public static final String PUT = "PUT";
10 | public static final String PATCH = "PATCH";
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/HttpResponseProxy.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | import javax.net.ssl.SSLSession;
4 | import java.net.URI;
5 | import java.net.http.HttpClient;
6 | import java.net.http.HttpHeaders;
7 | import java.net.http.HttpRequest;
8 | import java.net.http.HttpResponse;
9 | import java.util.*;
10 |
11 | public class HttpResponseProxy implements HttpResponse {
12 |
13 | private final int statusCode;
14 | private final HttpHeaders headers;
15 | private final HttpRequest request;
16 | private final T body;
17 |
18 | public HttpResponseProxy(int statusCode, HttpHeaders headers, T body, HttpRequest request) {
19 | this.request = request;
20 | this.statusCode = statusCode;
21 | this.headers = headers;
22 | this.body = body;
23 | }
24 |
25 | @Override
26 | public int statusCode() {
27 | return statusCode;
28 | }
29 |
30 | @Override
31 | public HttpRequest request() {
32 | return request;
33 | }
34 |
35 | @Override
36 | public Optional> previousResponse() {
37 | return Optional.empty();
38 | }
39 |
40 | @Override
41 | public HttpHeaders headers() {
42 | return headers;
43 | }
44 |
45 | @Override
46 | public T body() {
47 | return body;
48 | }
49 |
50 | @Override
51 | public Optional sslSession() {
52 | return Optional.empty();
53 | }
54 |
55 | @Override
56 | public URI uri() {
57 | return request.uri();
58 | }
59 |
60 | @Override
61 | public HttpClient.Version version() {
62 | return request.version().orElse(HttpClient.Version.HTTP_1_1);
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/ParameterValue.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | import java.util.List;
4 |
5 | class ParameterValue {
6 |
7 | final String name;
8 | final List values;
9 |
10 | public ParameterValue(String name, List values) {
11 | this.name = name;
12 | this.values = values;
13 | }
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | public List getValues() {
20 | return values;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/PeekSubscriber.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.concurrent.Flow;
5 |
6 | public class PeekSubscriber implements Flow.Subscriber {
7 |
8 | private Flow.Subscription subscription;
9 | private ByteBuffer content;
10 |
11 | @Override
12 | public void onSubscribe(Flow.Subscription subscription) {
13 | this.subscription = subscription;
14 | this.subscription.request(1);
15 | }
16 |
17 | @Override
18 | public void onNext(ByteBuffer item) {
19 | content = item;
20 | this.subscription.request(1);
21 | }
22 |
23 | @Override
24 | public void onError(Throwable throwable) {
25 | throwable.printStackTrace();
26 | }
27 |
28 | @Override
29 | public void onComplete() {
30 |
31 | }
32 |
33 | public ByteBuffer content() {
34 | return content;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/UrlConditions.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | import com.pgssoft.httpclient.internal.debug.Debugger;
4 | import org.hamcrest.Matcher;
5 | import org.hamcrest.Matchers;
6 | import org.hamcrest.StringDescription;
7 |
8 | import java.net.URI;
9 | import java.net.URISyntaxException;
10 | import java.net.http.HttpRequest;
11 |
12 | import static org.hamcrest.Matchers.equalTo;
13 |
14 | public class UrlConditions {
15 |
16 | private static final int EMPTY_PORT = -1;
17 | private UrlParamsMatcher parameterConditions = new UrlParamsMatcher();
18 | private Matcher referenceCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue());
19 | private Matcher hostCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue());
20 | private Matcher pathCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue());
21 | private Matcher portCondition = Matchers.any(Integer.class);
22 | private Matcher schemaCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue());
23 |
24 | public static UrlConditions parse(String urlText) {
25 | try {
26 | UrlConditions conditions = new UrlConditions();
27 | URI uri = new URI(urlText);
28 | UrlParams params = UrlParams.parse(uri.getQuery());
29 | conditions.setSchemaCondition(getStringMatcher(uri.getScheme()));
30 | conditions.setHostCondition(getStringMatcher(uri.getHost()));
31 | conditions.setPortCondition(equalTo(uri.getPort()));
32 | conditions.setPathCondition(getStringMatcher(uri.getPath()));
33 | conditions.setReferenceCondition(getStringMatcher(uri.getFragment()));
34 | conditions.setParameterConditions(new UrlParamsMatcher(params));
35 | return conditions;
36 | } catch (URISyntaxException e) {
37 | throw new IllegalArgumentException(e);
38 | }
39 | }
40 |
41 |
42 | private static Matcher getStringMatcher(String val) {
43 | if (val == null || val.isEmpty()) {
44 | return Matchers.isEmptyOrNullString();
45 | } else {
46 | return Matchers.equalTo(val);
47 | }
48 | }
49 |
50 | public boolean matches(URI uri) {
51 | return hostCondition.matches(uri.getHost())
52 | && pathCondition.matches(uri.getPath())
53 | && portCondition.matches(uri.getPort())
54 | && referenceCondition.matches(uri.getFragment())
55 | && schemaCondition.matches(uri.getScheme())
56 | && parameterConditions.matches(uri.getQuery());
57 |
58 | }
59 |
60 | public void debug(HttpRequest request, Debugger debugger) {
61 | URI uri = request.uri();
62 | debugger.message(schemaCondition.matches(uri.getScheme()), "schema is " + describeMatcher(schemaCondition));
63 | debugger.message(hostCondition.matches(uri.getHost()), "host is " + describeMatcher(hostCondition));
64 | debugger.message(portCondition.matches(uri.getPort()), "port is " + portDebugDescription());
65 | debugger.message(pathCondition.matches(uri.getPath()), "path is " + describeMatcher(pathCondition));
66 | debugger.message(parameterConditions.matches(uri.getQuery()), "all URI parameters have matching value");
67 | debugger.message(referenceCondition.matches(uri.getFragment()), "URI reference has matching value");
68 | }
69 |
70 | private String portDebugDescription() {
71 | if (portCondition.matches(EMPTY_PORT)) {
72 | return "empty";
73 | } else {
74 | return describeMatcher(portCondition);
75 | }
76 | }
77 |
78 | private String describeMatcher(Matcher> matcher) {
79 | return StringDescription.toString(matcher);
80 | }
81 |
82 |
83 | public UrlParamsMatcher getParameterConditions() {
84 | return parameterConditions;
85 | }
86 |
87 | public void setParameterConditions(UrlParamsMatcher parameterConditions) {
88 | this.parameterConditions = parameterConditions;
89 | }
90 |
91 | public Matcher getReferenceCondition() {
92 | return referenceCondition;
93 | }
94 |
95 | public void setReferenceCondition(Matcher referenceCondition) {
96 | this.referenceCondition = referenceCondition;
97 | }
98 |
99 | public Matcher getHostCondition() {
100 | return hostCondition;
101 | }
102 |
103 | public void setHostCondition(Matcher hostCondition) {
104 | this.hostCondition = hostCondition;
105 | }
106 |
107 | public Matcher getPathCondition() {
108 | return pathCondition;
109 | }
110 |
111 | public void setPathCondition(Matcher pathCondition) {
112 | this.pathCondition = pathCondition;
113 | }
114 |
115 | public Matcher getPortCondition() {
116 | return portCondition;
117 | }
118 |
119 | public void setPortCondition(Matcher portCondition) {
120 | this.portCondition = portCondition;
121 | }
122 |
123 | public Matcher getSchemaCondition() {
124 | return schemaCondition;
125 | }
126 |
127 | public void setSchemaCondition(Matcher schemaCondition) {
128 | this.schemaCondition = schemaCondition;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/UrlParams.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | import java.util.*;
4 | import java.util.stream.Collectors;
5 |
6 | import static java.util.stream.Collectors.mapping;
7 | import static java.util.stream.Collectors.toList;
8 |
9 | class UrlParams {
10 |
11 | private final ArrayList params = new ArrayList<>();
12 |
13 | boolean contains(String name) {
14 | return params.stream().anyMatch(p -> p.getName().equals(name));
15 | }
16 |
17 | public List getParams() {
18 | return params;
19 | }
20 |
21 |
22 | static UrlParams parse(String query) {
23 | if (query == null) {
24 | return new UrlParams();
25 | } else {
26 | UrlParams urlParams = new UrlParams();
27 | splitQuery(query).forEach((k, v) -> urlParams.params.add(new ParameterValue(k, v)));
28 | return urlParams;
29 | }
30 | }
31 |
32 |
33 | private static Map> splitQuery(String query) {
34 | if (query == null || query.length() <= 0) {
35 | return Collections.emptyMap();
36 | }
37 |
38 | return Arrays.stream(query.split("&"))
39 | .map(UrlParams::splitQueryParameter)
40 | .collect(Collectors.groupingBy(AbstractMap.SimpleImmutableEntry::getKey, LinkedHashMap::new, mapping(Map.Entry::getValue, toList())));
41 | }
42 |
43 | private static AbstractMap.SimpleImmutableEntry splitQueryParameter(String it) {
44 | final int idx = it.indexOf("=");
45 | final String key = idx > 0 ? it.substring(0, idx) : it;
46 | final String value = idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : null;
47 | return new AbstractMap.SimpleImmutableEntry<>(key, value);
48 | }
49 |
50 | public Set getNames() {
51 | return params.stream().map(ParameterValue::getName).collect(Collectors.toSet());
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/UrlParamsMatcher.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | import org.hamcrest.Matcher;
4 | import org.hamcrest.Matchers;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.Set;
9 |
10 | public class UrlParamsMatcher {
11 |
12 | private Map>> matchers;
13 |
14 | public UrlParamsMatcher(UrlParams params) {
15 | matchers = new HashMap<>();
16 | for (ParameterValue param : params.getParams()) {
17 | String[] values = param.getValues().toArray(new String[]{});
18 | matchers.put(param.getName(), Matchers.containsInAnyOrder(values));
19 | }
20 | }
21 |
22 | public UrlParamsMatcher() {
23 | }
24 |
25 |
26 | private boolean setsOfParametersAreEqual(String query) {
27 | Set expectedParams = matchers.keySet();
28 | Set actualParams = UrlParams.parse(query).getNames();
29 | return expectedParams.equals(actualParams);
30 | }
31 |
32 | private boolean allParamsHaveMatchingValue(String query) {
33 | UrlParams params = UrlParams.parse(query);
34 | return params.getParams().stream()
35 | .allMatch(param -> matchers.get(param.getName()).matches(param.getValues()));
36 | }
37 |
38 |
39 | public boolean matches(String query) {
40 | return noMatchersWereDefined() ||
41 | (setsOfParametersAreEqual(query) && allParamsHaveMatchingValue(query));
42 | }
43 |
44 | private boolean noMatchersWereDefined() {
45 | return matchers == null;
46 | }
47 |
48 | public void addParam(String name, Matcher> matcher) {
49 | if (noMatchersWereDefined()) {
50 | matchers = new HashMap<>();
51 | }
52 | matchers.put(name, matcher);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/action/ActionBundle.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.action;
2 |
3 | import com.pgssoft.httpclient.Action;
4 |
5 | import java.util.LinkedList;
6 |
7 | public final class ActionBundle extends LinkedList {
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/action/SetBodyStringAction.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.action;
2 |
3 | import com.pgssoft.httpclient.Action;
4 | import com.pgssoft.httpclient.MockedServerResponse;
5 |
6 | import java.nio.charset.Charset;
7 |
8 | public final class SetBodyStringAction implements Action {
9 |
10 | private final String content;
11 | private final Charset charset;
12 |
13 | public SetBodyStringAction(String content, Charset charset) {
14 | this.content = content;
15 | this.charset = charset;
16 | }
17 |
18 | @Override
19 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) {
20 | responseBuilder.setBodyBytes(charset.encode(content));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/action/SetHeaderAction.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.action;
2 |
3 | import com.pgssoft.httpclient.Action;
4 | import com.pgssoft.httpclient.MockedServerResponse;
5 |
6 |
7 | public final class SetHeaderAction implements Action {
8 |
9 | private final String key, value;
10 |
11 | public SetHeaderAction(String key, String value) {
12 | this.key = key;
13 | this.value = value;
14 | }
15 |
16 | @Override
17 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) {
18 | responseBuilder.addHeader(key, value);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/action/SetStatusAction.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.action;
2 |
3 | import com.pgssoft.httpclient.Action;
4 | import com.pgssoft.httpclient.MockedServerResponse;
5 |
6 | public final class SetStatusAction implements Action {
7 |
8 | private final int status;
9 |
10 | public SetStatusAction(int status) {
11 | this.status = status;
12 | }
13 |
14 | @Override
15 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) {
16 | responseBuilder.setStatusCode(status);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/action/ThrowExceptionAction.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.action;
2 |
3 | import com.pgssoft.httpclient.Action;
4 | import com.pgssoft.httpclient.MockedServerResponse;
5 |
6 | import java.io.IOException;
7 |
8 | public final class ThrowExceptionAction implements Action {
9 |
10 | private final IOException exception;
11 |
12 | public ThrowExceptionAction(IOException exception) {
13 | this.exception = exception;
14 | }
15 |
16 | @Override
17 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) throws IOException {
18 | throw exception;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/condition/BodyCondition.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.condition;
2 |
3 | import com.pgssoft.httpclient.Condition;
4 | import com.pgssoft.httpclient.internal.PeekSubscriber;
5 | import org.hamcrest.Matcher;
6 |
7 | import java.net.http.HttpRequest;
8 | import java.nio.charset.StandardCharsets;
9 | import java.util.Optional;
10 |
11 | public final class BodyCondition implements Condition {
12 |
13 | private final Matcher matcher;
14 |
15 | public BodyCondition(Matcher matcher) {
16 | this.matcher = matcher;
17 | }
18 |
19 | @Override
20 | public boolean matches(HttpRequest request) {
21 | final Optional bodyPublisher = request.bodyPublisher();
22 | if (bodyPublisher.isEmpty()) {
23 | return matcher.matches(null);
24 | }
25 |
26 | final var subscriber = new PeekSubscriber();
27 | request.bodyPublisher().orElseThrow().subscribe(subscriber);
28 | final var content = subscriber.content();
29 | return content != null && matcher.matches(new String(content.array(), StandardCharsets.UTF_8));
30 | }
31 |
32 | @Override
33 | public String getDebugMessage() {
34 | return "body matches";
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/condition/HeaderCondition.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.condition;
2 |
3 | import com.pgssoft.httpclient.Condition;
4 | import org.hamcrest.Matcher;
5 | import org.hamcrest.StringDescription;
6 |
7 | import java.net.http.HttpRequest;
8 | import java.util.Objects;
9 |
10 | public final class HeaderCondition implements Condition {
11 |
12 | private final String header;
13 | private final Matcher expectedValue;
14 |
15 | public HeaderCondition(String header, Matcher expectedValue) {
16 | this.header = header;
17 | this.expectedValue = expectedValue;
18 | }
19 |
20 | @Override
21 | public boolean matches(HttpRequest request) {
22 | return request.headers().allValues(header)
23 | .stream()
24 | .filter(Objects::nonNull)
25 | .anyMatch(expectedValue::matches);
26 | }
27 |
28 | @Override
29 | public String getDebugMessage() {
30 | return "header " + header + " is " + StringDescription.toString(expectedValue);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/condition/MethodCondition.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.condition;
2 |
3 | import com.pgssoft.httpclient.Condition;
4 |
5 | import java.net.http.HttpRequest;
6 |
7 | public final class MethodCondition implements Condition {
8 |
9 | private final String method;
10 |
11 | public MethodCondition(String method) {
12 | this.method = method;
13 | }
14 |
15 | @Override
16 | public boolean matches(HttpRequest request) {
17 | return request.method().equals(method);
18 | }
19 |
20 | @Override
21 | public String getDebugMessage() {
22 | return "HTTP method is " + method;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/debug/Debugger.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.debug;
2 |
3 | import com.pgssoft.httpclient.internal.rule.Rule;
4 |
5 | import java.net.http.HttpRequest;
6 | import java.util.List;
7 |
8 | public class Debugger {
9 |
10 | public void debug(List rules, HttpRequest request) {
11 | logRequest(request);
12 | logRules(rules, request);
13 | }
14 |
15 | private void logRules(List rules, HttpRequest request) {
16 | if (rules.size() == 0) {
17 | System.out.println("No rules were defined.");
18 | }
19 | for (int i = 0; i < rules.size(); i++) {
20 | System.out.println("Rule " + (i + 1) + ":");
21 | System.out.println("\tMATCHES\t\tEXPECTED");
22 | rules.get(i).debug(request, this);
23 | }
24 | System.out.println();
25 | System.out.println("----------------");
26 |
27 | }
28 |
29 | private void logRequest(HttpRequest request) {
30 | System.out.println("Request: " + request.method() + " " + request.uri());
31 | }
32 |
33 | public void message(boolean matches, String expected) {
34 | String debugMessage = String.format("\t%s\t\t%s", matches, expected);
35 | System.out.println(debugMessage);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/rule/Rule.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.rule;
2 |
3 | import com.pgssoft.httpclient.MockedServerResponse;
4 | import com.pgssoft.httpclient.internal.UrlConditions;
5 | import com.pgssoft.httpclient.Action;
6 | import com.pgssoft.httpclient.internal.action.ActionBundle;
7 | import com.pgssoft.httpclient.Condition;
8 | import com.pgssoft.httpclient.internal.debug.Debugger;
9 |
10 | import java.io.IOException;
11 | import java.net.http.HttpRequest;
12 | import java.util.List;
13 | import java.util.Queue;
14 |
15 | public final class Rule {
16 |
17 | private final UrlConditions urlConditions;
18 | private final List conditions;
19 | private final Queue actionBundles;
20 |
21 | public Rule(UrlConditions urlConditions, List conditions, Queue actionBundles) {
22 | this.urlConditions = urlConditions;
23 | this.conditions = conditions;
24 | this.actionBundles = actionBundles;
25 | }
26 |
27 | public boolean matches(HttpRequest request) {
28 | return urlConditions.matches(request.uri()) &&
29 | conditions.stream().allMatch(c -> c.matches(request));
30 | }
31 |
32 | public MockedServerResponse produceResponse() throws IOException {
33 | final var responseBuilder = new MockedServerResponse.Builder();
34 |
35 | final var actionBundle = actionBundles.size() > 1 ? actionBundles.poll() : actionBundles.peek();
36 | for (Action a : actionBundle) {
37 | a.enrichResponse(responseBuilder);
38 | }
39 |
40 | return responseBuilder.build();
41 | }
42 |
43 | public void debug(HttpRequest request, Debugger debugger) {
44 | for (Condition condition : conditions) {
45 | debugCondition(condition, request, debugger);
46 | }
47 | urlConditions.debug(request, debugger);
48 | }
49 |
50 | private void debugCondition(Condition condition, HttpRequest request, Debugger debugger) {
51 | debugger.message(condition.matches(request), condition.getDebugMessage());
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/pgssoft/httpclient/internal/rule/RuleBuilder.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal.rule;
2 |
3 | import com.pgssoft.httpclient.internal.UrlConditions;
4 | import com.pgssoft.httpclient.Action;
5 | import com.pgssoft.httpclient.internal.action.ActionBundle;
6 | import com.pgssoft.httpclient.Condition;
7 | import com.pgssoft.httpclient.internal.condition.MethodCondition;
8 | import org.hamcrest.Matcher;
9 | import org.hamcrest.Matchers;
10 |
11 | import java.util.*;
12 |
13 | public final class RuleBuilder {
14 |
15 | private final Deque actionBundles = new LinkedList<>();
16 | private final List conditions = new ArrayList<>();
17 | private final UrlConditions urlConditions;
18 |
19 | public RuleBuilder(String method, String host, String url) {
20 | url = url.startsWith("/") ? host + url : url;
21 | addCondition(new MethodCondition(method));
22 | urlConditions = UrlConditions.parse(url);
23 | }
24 |
25 | public RuleBuilder(String method) {
26 | addCondition(new MethodCondition(method));
27 | urlConditions = new UrlConditions();
28 | }
29 |
30 | public void addAction(Action action) {
31 | var bundle = actionBundles.peekLast();
32 | if (bundle == null) {
33 | bundle = new ActionBundle();
34 | actionBundles.add(bundle);
35 | }
36 | bundle.add(action);
37 | }
38 |
39 | public void addActionBundle(Action action) {
40 | final var bundle = new ActionBundle();
41 | bundle.add(action);
42 | actionBundles.add(bundle);
43 | }
44 |
45 | public void addCondition(Condition condition) {
46 | conditions.add(condition);
47 | }
48 |
49 |
50 | public void setParameterCondition(String name, Matcher matcher) {
51 | urlConditions.getParameterConditions().addParam(name, Matchers.containsInAnyOrder(List.of(matcher)));
52 | }
53 |
54 | public void setReferenceCondition(Matcher matcher) {
55 | urlConditions.setReferenceCondition(matcher);
56 | }
57 |
58 | public void addHostCondition(String host) {
59 | urlConditions.setHostCondition(Matchers.equalTo(host));
60 | }
61 |
62 | public void setPathCondition(Matcher matcher) {
63 | urlConditions.setPathCondition(matcher);
64 | }
65 |
66 | public Rule build() {
67 | return new Rule(urlConditions, conditions, actionBundles);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.pgssoft.httpclient {
2 | requires java.net.http;
3 | requires hamcrest.all;
4 |
5 | exports com.pgssoft.httpclient;
6 | }
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/DebuggerTest.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import com.pgssoft.httpclient.internal.debug.Debugger;
4 | import com.pgssoft.httpclient.internal.rule.Rule;
5 | import org.hamcrest.Matchers;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import java.net.URI;
10 | import java.net.http.HttpRequest;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import static com.pgssoft.httpclient.TestRequests.*;
15 | import static java.net.http.HttpRequest.BodyPublishers.noBody;
16 | import static java.net.http.HttpRequest.newBuilder;
17 | import static java.net.http.HttpResponse.BodyHandlers.discarding;
18 | import static java.net.http.HttpResponse.BodyHandlers.ofString;
19 | import static org.hamcrest.MatcherAssert.assertThat;
20 | import static org.hamcrest.Matchers.hasItem;
21 | import static org.hamcrest.Matchers.not;
22 | import static org.junit.jupiter.api.Assertions.*;
23 |
24 | public class DebuggerTest {
25 |
26 | private HttpClientMock httpClientMock;
27 | private TestDebugger debugger;
28 |
29 | @BeforeEach
30 | void setUp() {
31 | debugger = new TestDebugger();
32 | httpClientMock = new HttpClientMock("http://localhost", debugger);
33 | }
34 |
35 | @Test
36 | void should_print_all_request_with_no_matching_rules() throws Exception {
37 | httpClientMock.onGet("/admin").doReturn("admin");
38 |
39 | try {
40 | httpClientMock.send(get("http://localhost/login"), discarding());
41 | httpClientMock.send(newBuilder(URI.create("http://localhost/admin")).GET().build(), discarding());
42 | } catch (IllegalStateException e) {
43 | // discard exception
44 | }
45 |
46 | assertThat(debugger.requests, hasItem("http://localhost/login"));
47 | assertThat(debugger.requests, not(hasItem("http://localhost/admin")));
48 | }
49 |
50 | @Test
51 | void should_print_all_request_when_debugging_is_turn_on() throws Exception {
52 | httpClientMock.onGet("/login").doReturn("login");
53 | httpClientMock.onGet("/user").doReturn("user");
54 | httpClientMock.onGet("/admin").doReturn("admin");
55 |
56 | httpClientMock.debugOn();
57 | httpClientMock.send(get("http://localhost/login"), discarding());
58 | httpClientMock.send(newBuilder(URI.create("http://localhost/user")).GET().build(), discarding());
59 | httpClientMock.debugOff();
60 | httpClientMock.send(newBuilder(URI.create("http://localhost/admin")).GET().build(), discarding());
61 |
62 | assertThat(debugger.requests, hasItem("http://localhost/login"));
63 | assertThat(debugger.requests, hasItem("http://localhost/user"));
64 | assertThat(debugger.requests, not(hasItem("http://localhost/admin")));
65 | }
66 |
67 | @Test
68 | void should_debug_header_condition() throws Exception {
69 | httpClientMock
70 | .onGet("/login").withHeader("User-Agent", "Mozilla")
71 | .doReturn("mozilla");
72 |
73 | try {
74 | httpClientMock.debugOn();
75 | httpClientMock.send(newBuilder(URI.create("http://localhost/login")).GET().header("User-Agent", "Mozilla").build(), ofString());
76 | httpClientMock.send(newBuilder(URI.create("http://localhost/login")).GET().header("User-Agent", "Chrome").build(), ofString());
77 | httpClientMock.debugOff();
78 | } catch (IllegalStateException e) {
79 | // discard the exception
80 | }
81 |
82 | assertTrue(debugger.matching.contains("header User-Agent is \"Mozilla\""));
83 | assertFalse(debugger.notMatching.contains("header User-Agent is \"Chrome\""));
84 | }
85 |
86 | @Test
87 | void should_put_message_about_missing_parameter() throws Exception {
88 | httpClientMock.onGet("/login").withParameter("foo", "bar");
89 | try {
90 | httpClientMock.send(get("http://localhost/login"), discarding());
91 | } catch (IllegalStateException e) {
92 | // discard exception
93 | }
94 | assertTrue(debugger.notMatching.contains("all URI parameters have matching value"));
95 | }
96 |
97 | @Test
98 | void should_put_message_about_matching_parameter() throws Exception {
99 | httpClientMock
100 | .onGet("/login").withParameter("foo", "bar")
101 | .doReturn("login");
102 | httpClientMock.debugOn();
103 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=bar")).GET().build(), discarding());
104 | assertTrue(debugger.matching.contains("all URI parameters have matching value"));
105 | }
106 |
107 | @Test
108 | void should_put_message_about_not_matching_parameter() throws Exception {
109 | httpClientMock.onGet("/login")
110 | .withParameter("foo", "bar")
111 | .doReturn("login");
112 | try {
113 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=bbb")).GET().build(), discarding());
114 | } catch (IllegalStateException e) {
115 | // discard exception
116 | }
117 | assertTrue(debugger.notMatching.contains("all URI parameters have matching value"));
118 | }
119 |
120 | @Test
121 | void should_put_message_about_redundant_parameter() throws Exception {
122 | httpClientMock.onGet("/login")
123 | .doReturn("login");
124 | try {
125 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=bbb")).GET().build(), discarding());
126 | } catch (IllegalStateException e) {
127 | // discard exception
128 | }
129 | assertTrue(debugger.notMatching.contains("all URI parameters have matching value"));
130 | }
131 |
132 | @Test
133 | void should_put_message_with_all_parameter_matchers() throws Exception {
134 | httpClientMock.onGet("/login")
135 | .withParameter("foo", Matchers.allOf(Matchers.startsWith("a"), Matchers.endsWith("b")))
136 | .doReturn("login");
137 | httpClientMock.debugOn();
138 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=aabb")).GET().build(), discarding());
139 | assertTrue(debugger.matching.contains("all URI parameters have matching value"));
140 | }
141 |
142 | @Test
143 | void should_put_message_about_not_matching_reference() throws Exception {
144 | httpClientMock.onGet("/login#foo")
145 | .doReturn("login");
146 | try {
147 | httpClientMock.send(get("http://localhost/login"), discarding());
148 | } catch (IllegalStateException e) {
149 | // discard exception
150 | }
151 | assertTrue(debugger.notMatching.contains("URI reference has matching value"));
152 | }
153 |
154 | @Test
155 | void should_put_message_about_matching_reference() throws Exception {
156 | httpClientMock.onGet("/login#foo")
157 | .doReturn("login");
158 | httpClientMock.debugOn();
159 | httpClientMock.send(newBuilder(URI.create("http://localhost/login#foo")).GET().build(), discarding());
160 | assertTrue(debugger.matching.contains("URI reference has matching value"));
161 | }
162 |
163 |
164 | @Test
165 | void should_put_message_about_matching_http_method() throws Exception {
166 | httpClientMock.onGet("/login").doReturn("login");
167 | httpClientMock.debugOn();
168 | httpClientMock.send(get("http://localhost/login"), discarding());
169 | assertTrue(debugger.matching.contains("HTTP method is GET"));
170 | }
171 |
172 | @Test
173 | void should_put_message_about_not_matching_http_method() throws Exception {
174 | httpClientMock.onGet("/login").doReturn("login");
175 | httpClientMock.debugOn();
176 | try {
177 | httpClientMock.send(post("http://localhost/login"), discarding());
178 | } catch (IllegalStateException e) {
179 | // discard exception
180 | }
181 | assertTrue(debugger.notMatching.contains("HTTP method is GET"));
182 | }
183 |
184 | @Test
185 | void should_put_message_about_not_matching_URL() throws Exception {
186 | httpClientMock.onGet("http://localhost:8080/login").doReturn("login");
187 | httpClientMock.debugOn();
188 | try {
189 | httpClientMock.send(newBuilder(URI.create("https://www.google.com")).POST(noBody()).build(), discarding());
190 | } catch (IllegalStateException e) {
191 | // discard exception
192 | }
193 | assertTrue(debugger.notMatching.contains("schema is \"http\""));
194 | assertTrue(debugger.notMatching.contains("host is \"localhost\""));
195 | assertTrue(debugger.notMatching.contains("path is \"/login\""));
196 | assertTrue(debugger.notMatching.contains("port is <8080>"));
197 | }
198 |
199 | @Test
200 | void should_put_message_about_matching_URL() {
201 | httpClientMock.onGet("http://localhost:8080/login").doReturn("login");
202 | httpClientMock.debugOn();
203 |
204 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost:8080/login"), discarding()));
205 | assertTrue(debugger.matching.contains("schema is \"http\""));
206 | assertTrue(debugger.matching.contains("host is \"localhost\""));
207 | assertTrue(debugger.matching.contains("path is \"/login\""));
208 | assertTrue(debugger.matching.contains("port is <8080>"));
209 | }
210 |
211 | @Test
212 | void should_use_anonymous_message_for_conditions_without_debug_message() throws Exception {
213 | httpClientMock.onGet("http://localhost:8080/login")
214 | .with(new TestCondition())
215 | .doReturn("login");
216 | httpClientMock.debugOn();
217 | try {
218 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), discarding());
219 | } catch (IllegalStateException e) {
220 | // discard exception
221 | }
222 | assertTrue(debugger.matching.contains("Anonymous condition"));
223 | }
224 |
225 | @Test
226 | void should_use_anonymous_message_for_lambda_conditions() throws Exception {
227 | httpClientMock.onGet("http://localhost:8080/login")
228 | .with(req -> true)
229 | .doReturn("login");
230 | httpClientMock.debugOn();
231 | try {
232 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), discarding());
233 | } catch (IllegalStateException e) {
234 | // discard exception
235 | }
236 | assertTrue(debugger.matching.contains("Anonymous condition"));
237 | }
238 |
239 |
240 | private class TestDebugger extends Debugger {
241 | private final ArrayList matching = new ArrayList<>();
242 | private final ArrayList notMatching = new ArrayList<>();
243 | private final ArrayList requests = new ArrayList<>();
244 |
245 | @Override
246 | public void debug(List rules, HttpRequest request) {
247 | this.requests.add(request.uri().toString());
248 | super.debug(rules, request);
249 | }
250 |
251 | @Override
252 | public void message(boolean matching, String expected) {
253 | super.message(matching, expected);
254 | if (matching) {
255 | this.matching.add(expected);
256 | } else {
257 | this.notMatching.add(expected);
258 | }
259 | }
260 | }
261 | }
262 |
263 |
264 | class TestCondition implements Condition {
265 |
266 | @Override
267 | public boolean matches(HttpRequest request) {
268 | return true;
269 | }
270 | }
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/HttpClientMockAsyncTest.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.net.URI;
6 | import java.net.http.HttpResponse;
7 | import java.util.concurrent.ExecutionException;
8 |
9 | import static java.net.http.HttpRequest.BodyPublishers.noBody;
10 | import static java.net.http.HttpRequest.newBuilder;
11 | import static org.hamcrest.CoreMatchers.equalTo;
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 |
14 | class HttpClientMockAsyncTest {
15 |
16 | @Test
17 | void sendAsync_Should_ReturnCompletedFuture() throws ExecutionException, InterruptedException {
18 | HttpClientMock httpClientMock = new HttpClientMock();
19 |
20 | httpClientMock.onPost()
21 | .withHost("localhost")
22 | .withPath("/login")
23 | .doReturn(200, "ABC");
24 |
25 | var req = newBuilder(URI.create("http://localhost/login"))
26 | .POST(noBody())
27 | .build();
28 | var res = httpClientMock.sendAsync(req, HttpResponse.BodyHandlers.ofString());
29 |
30 | assertThat(res.get().body(), equalTo("ABC"));
31 | assertThat(res.get().statusCode(), equalTo(200));
32 | httpClientMock.verify().post("http://localhost/login");
33 | }
34 |
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/HttpClientMockBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import org.hamcrest.MatcherAssert;
4 | import org.hamcrest.Matchers;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.io.IOException;
8 | import java.net.URI;
9 | import java.net.URISyntaxException;
10 | import java.net.http.HttpClient;
11 | import java.net.http.HttpRequest;
12 | import java.net.http.HttpResponse;
13 |
14 | import static com.pgssoft.httpclient.TestRequests.get;
15 | import static com.pgssoft.httpclient.TestRequests.post;
16 | import static java.net.http.HttpRequest.BodyPublishers.noBody;
17 | import static java.net.http.HttpRequest.newBuilder;
18 | import static java.net.http.HttpResponse.BodyHandlers.discarding;
19 | import static java.net.http.HttpResponse.BodyHandlers.ofString;
20 | import static org.hamcrest.CoreMatchers.equalTo;
21 | import static org.hamcrest.MatcherAssert.assertThat;
22 | import static org.hamcrest.Matchers.containsString;
23 | import static org.junit.jupiter.api.Assertions.assertThrows;
24 | import static org.junit.jupiter.api.Assertions.assertTrue;
25 |
26 | class HttpClientMockBuilderTest {
27 |
28 | @Test
29 | void shouldMatchSeparateHostAndPath() throws Exception {
30 | HttpClientMock httpClientMock = new HttpClientMock();
31 |
32 | httpClientMock.onPost()
33 | .withHost("localhost")
34 | .withPath("/login")
35 | .doReturnStatus(200);
36 |
37 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://www.google.com/login"), discarding()));
38 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/foo"), discarding()));
39 | assertThat(httpClientMock.send(post("http://localhost/login"), discarding()).statusCode(), equalTo(200));
40 | }
41 |
42 | @Test
43 | void shouldMatchSeparatePathAndParameter() throws Exception {
44 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
45 |
46 | httpClientMock.onPost()
47 | .withPath("/login")
48 | .withParameter("a", "1")
49 | .doReturn("one");
50 | httpClientMock.onPost()
51 | .withPath("/login")
52 | .withParameter("b", "2")
53 | .doReturn("two");
54 |
55 | var firstResponse = httpClientMock.send(
56 | newBuilder(URI.create("http://localhost/login?a=1"))
57 | .POST(noBody())
58 | .build(),
59 | ofString()
60 | );
61 |
62 | var secondResponse = httpClientMock.send(
63 | newBuilder(URI.create("http://localhost/login?b=2"))
64 | .POST(noBody())
65 | .build(),
66 | ofString()
67 | );
68 |
69 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login?a=1&b=2"), ofString()));
70 |
71 | MatcherAssert.assertThat(firstResponse, HttpResponseMatchers.hasContent("one"));
72 | MatcherAssert.assertThat(secondResponse, HttpResponseMatchers.hasContent("two"));
73 | }
74 |
75 | @Test
76 | void onXXXX_methods_with_path_should_add_method_and_path_condition() throws Exception {
77 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
78 |
79 | httpClientMock.onGet("/foo").doReturn("get");
80 | httpClientMock.onPost("/foo").doReturn("post");
81 | httpClientMock.onPut("/foo").doReturn("put");
82 | httpClientMock.onDelete("/foo").doReturn("delete");
83 | httpClientMock.onHead("/foo").doReturn("head");
84 | httpClientMock.onOptions("/foo").doReturn("options");
85 | httpClientMock.onPatch("/foo").doReturn("patch");
86 |
87 | var getResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
88 | var postResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).POST(noBody()).build(), ofString());
89 | var putResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).PUT(noBody()).build(), ofString());
90 | var deleteResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).DELETE().build(), ofString());
91 | var headResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("HEAD", noBody()).build(), ofString());
92 | var optionsResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("OPTIONS", noBody()).build(), ofString());
93 | var patchResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("PATCH", noBody()).build(), ofString());
94 |
95 | MatcherAssert.assertThat(getResponse, HttpResponseMatchers.hasContent("get"));
96 | MatcherAssert.assertThat(postResponse, HttpResponseMatchers.hasContent("post"));
97 | MatcherAssert.assertThat(putResponse, HttpResponseMatchers.hasContent("put"));
98 | MatcherAssert.assertThat(deleteResponse, HttpResponseMatchers.hasContent("delete"));
99 | MatcherAssert.assertThat(headResponse, HttpResponseMatchers.hasContent("head"));
100 | MatcherAssert.assertThat(optionsResponse, HttpResponseMatchers.hasContent("options"));
101 | MatcherAssert.assertThat(patchResponse, HttpResponseMatchers.hasContent("patch"));
102 | }
103 |
104 |
105 | @Test
106 | void onXXXX_methods_should_add_method_condition() throws Exception {
107 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
108 |
109 | httpClientMock.onGet().doReturn("get");
110 | httpClientMock.onPost().doReturn("post");
111 | httpClientMock.onPut().doReturn("put");
112 | httpClientMock.onDelete().doReturn("delete");
113 | httpClientMock.onHead().doReturn("head");
114 | httpClientMock.onOptions().doReturn("options");
115 | httpClientMock.onPatch().doReturn("patch");
116 |
117 | var getResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
118 | var postResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).POST(noBody()).build(), ofString());
119 | var putResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).PUT(noBody()).build(), ofString());
120 | var deleteResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).DELETE().build(), ofString());
121 | var headResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("HEAD", noBody()).build(), ofString());
122 | var optionsResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("OPTIONS", noBody()).build(), ofString());
123 | var patchResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("PATCH", noBody()).build(), ofString());
124 |
125 | MatcherAssert.assertThat(getResponse, HttpResponseMatchers.hasContent("get"));
126 | MatcherAssert.assertThat(postResponse, HttpResponseMatchers.hasContent("post"));
127 | MatcherAssert.assertThat(putResponse, HttpResponseMatchers.hasContent("put"));
128 | MatcherAssert.assertThat(deleteResponse, HttpResponseMatchers.hasContent("delete"));
129 | MatcherAssert.assertThat(headResponse, HttpResponseMatchers.hasContent("head"));
130 | MatcherAssert.assertThat(optionsResponse, HttpResponseMatchers.hasContent("options"));
131 | MatcherAssert.assertThat(patchResponse, HttpResponseMatchers.hasContent("patch"));
132 | }
133 |
134 | @Test
135 | void shouldCheckCustomRule() throws Exception {
136 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
137 |
138 | final Condition fooCondition = request -> request.uri().toString().contains("foo");
139 | httpClientMock.onGet("http://localhost/foo/bar")
140 | .with(fooCondition)
141 | .doReturn("yes");
142 |
143 | final HttpResponse first = httpClientMock.send(newBuilder(URI.create("http://localhost/foo/bar")).GET().build(), ofString());
144 |
145 | MatcherAssert.assertThat(first, HttpResponseMatchers.hasContent("yes"));
146 | }
147 |
148 | @Test
149 | void shouldUseRightHostAndPath() throws Exception {
150 | HttpClientMock httpClientMock = new HttpClientMock();
151 |
152 | httpClientMock.onGet("http://localhost:8080/foo").doReturn("localhost");
153 | httpClientMock.onGet("http://www.google.com").doReturn("google");
154 | httpClientMock.onGet("https://www.google.com").doReturn("https");
155 |
156 | final HttpResponse localhost = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), ofString());
157 | final HttpResponse google = httpClientMock.send(get("http://www.google.com"), ofString());
158 | final HttpResponse https = httpClientMock.send(newBuilder(URI.create("https://www.google.com")).GET().build(), ofString());
159 |
160 | MatcherAssert.assertThat(localhost, HttpResponseMatchers.hasContent("localhost"));
161 | MatcherAssert.assertThat(google, HttpResponseMatchers.hasContent("google"));
162 | MatcherAssert.assertThat(https, HttpResponseMatchers.hasContent("https"));
163 | }
164 |
165 | @Test
166 | void shouldMatchRightHeaderValue() throws Exception {
167 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
168 |
169 | httpClientMock
170 | .onGet("/login").withHeader("User-Agent", "Mozilla")
171 | .doReturn("mozilla");
172 | httpClientMock
173 | .onGet("/login").withHeader("User-Agent", "Chrome")
174 | .doReturn("chrome");
175 |
176 | final var getMozilla = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Mozilla").build(), ofString());
177 | final var getChrome = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Chrome").build(), ofString());
178 | //final var getSafari = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Safari").build(), ofString());
179 |
180 | MatcherAssert.assertThat(getMozilla, HttpResponseMatchers.hasContent("mozilla"));
181 | MatcherAssert.assertThat(getChrome, HttpResponseMatchers.hasContent("chrome"));
182 | //assertThat(httpClientMock.execute(getSafari), hasStatus(404));
183 | }
184 |
185 | @Test
186 | void should_match_right_parameter_value() throws Exception {
187 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
188 |
189 | httpClientMock
190 | .onGet("/foo").withParameter("id", "1").withParameter("name", "abc")
191 | .doReturn("one");
192 | httpClientMock
193 | .onGet("/foo").withParameter("id", "2")
194 | .doReturn("two");
195 |
196 | final var one = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo?id=1&name=abc")).GET().build(), ofString());
197 | final var two = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo?id=2")).GET().build(), ofString());
198 |
199 | MatcherAssert.assertThat(one, HttpResponseMatchers.hasContent("one"));
200 | MatcherAssert.assertThat(two, HttpResponseMatchers.hasContent("two"));
201 | }
202 |
203 | @Test
204 | void should_add_default_host_to_every_relative_path() throws Exception {
205 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
206 |
207 | httpClientMock.onGet("/login").doReturn("login");
208 | httpClientMock.onGet("/product/search").doReturn("search");
209 | httpClientMock.onGet("/logout").doReturn("logout");
210 |
211 | final var login = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().build(), ofString());
212 | final var search = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/product/search")).GET().build(), ofString());
213 | final var logout = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/logout")).GET().build(), ofString());
214 |
215 | MatcherAssert.assertThat(login, HttpResponseMatchers.hasContent("login"));
216 | MatcherAssert.assertThat(search, HttpResponseMatchers.hasContent("search"));
217 | MatcherAssert.assertThat(logout, HttpResponseMatchers.hasContent("logout"));
218 | }
219 |
220 | @Test
221 | void checkBody() throws Exception {
222 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
223 |
224 | httpClientMock.onPost("/login")
225 | .doReturnStatus(500);
226 | httpClientMock.onPost("/login").withBody(containsString("foo"))
227 | .doReturnStatus(200);
228 |
229 | final var badLogin = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), discarding());
230 | final var correctLogin = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(HttpRequest.BodyPublishers.ofString("foo")).build(), discarding());
231 |
232 | MatcherAssert.assertThat(correctLogin, HttpResponseMatchers.hasStatus(200));
233 | MatcherAssert.assertThat(badLogin, HttpResponseMatchers.hasStatus(500));
234 | }
235 |
236 | @Test
237 | void when_url_contains_parameter_it_should_be_added_us_a_separate_condition() throws Exception {
238 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
239 |
240 | httpClientMock.onPost("/login?user=john")
241 | .doReturnStatus(400);
242 | httpClientMock.onPost("/login?user=john&pass=abc")
243 | .doReturnStatus(200);
244 |
245 | // HttpResponse notFound = httpClientMock.execute(new HttpPost("http://localhost/login"));
246 | // HttpResponse notFound_2 = httpClientMock.execute(new HttpPost("http://localhost/login?user=john&pass=abc&foo=bar"));
247 |
248 | final var wrong = httpClientMock.send(post("http://localhost/login?user=john"), discarding());
249 | final var ok = httpClientMock.send(post("http://localhost/login?user=john&pass=abc"), discarding());
250 |
251 | //assertThat(notFound, hasStatus(404));
252 | MatcherAssert.assertThat(wrong, HttpResponseMatchers.hasStatus(400));
253 | MatcherAssert.assertThat(ok, HttpResponseMatchers.hasStatus(200));
254 | //assertThat(notFound_2, hasStatus(404));
255 | }
256 |
257 | @Test
258 | void should_not_match_URL_with_missing_param() {
259 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
260 |
261 | httpClientMock.onPost("/login?user=john")
262 | .doReturnStatus(400);
263 | httpClientMock.onPost("/login?user=john&pass=abc")
264 | .doReturnStatus(200);
265 | var request = newBuilder(URI.create("http://localhost/login")).POST(noBody()).build();
266 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(request, discarding()));
267 | }
268 |
269 | @Test
270 | void should_not_match_URL_with_surplus_param() {
271 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
272 |
273 | httpClientMock.onPost("/login?user=john")
274 | .doReturnStatus(200);
275 | var request = newBuilder(URI.create("http://localhost/login?user=john&pass=abc")).POST(noBody()).build();
276 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(request, discarding()));
277 | }
278 |
279 |
280 | @Test
281 | void should_handle_path_with_parameters_and_reference() throws Exception {
282 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
283 |
284 | httpClientMock.onPost("/login?p=1#abc")
285 | .doReturnStatus(200);
286 |
287 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login"), discarding()));
288 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login?p=1"), discarding()));
289 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login#abc"), discarding()));
290 |
291 | final var ok = httpClientMock.send(newBuilder(URI.create("http://localhost/login?p=1#abc")).POST(noBody()).build(), discarding());
292 | MatcherAssert.assertThat(ok, HttpResponseMatchers.hasStatus(200));
293 | }
294 |
295 | @Test
296 | void should_check_reference_value() throws Exception {
297 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
298 |
299 | httpClientMock.onPost("/login")
300 | .doReturnStatus(400);
301 | httpClientMock.onPost("/login")
302 | .withReference("ref")
303 | .doReturnStatus(200);
304 |
305 | final var wrong = httpClientMock.send(newBuilder(URI.create("http://localhost/login")).POST(noBody()).build(), discarding());
306 | final var ok = httpClientMock.send(newBuilder(URI.create("http://localhost/login#ref")).POST(noBody()).build(), discarding());
307 |
308 | MatcherAssert.assertThat(wrong, HttpResponseMatchers.hasStatus(400));
309 | MatcherAssert.assertThat(ok, HttpResponseMatchers.hasStatus(200));
310 | }
311 |
312 | @Test
313 | void after_reset_every_call_should_throw_exception() {
314 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
315 |
316 | httpClientMock.onPost("/login").doReturnStatus(200);
317 | httpClientMock.reset();
318 |
319 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login"), discarding()));
320 | }
321 |
322 | @Test
323 | void after_reset_number_of_calls_should_be_zero() throws Exception {
324 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
325 |
326 | httpClientMock.onPost("/login").doReturnStatus(200);
327 | httpClientMock.send(post("http://localhost/login"), discarding());
328 | httpClientMock.send(post("http://localhost/login"), discarding());
329 | httpClientMock.reset();
330 | httpClientMock.verify().post("/login").notCalled();
331 |
332 | httpClientMock.onPost("/login").doReturnStatus(200);
333 | httpClientMock.send(post("http://localhost/login"), discarding());
334 | httpClientMock.send(post("http://localhost/login"), discarding());
335 | httpClientMock.verify().post("/login").called(2);
336 |
337 | }
338 |
339 | @Test
340 | void not_all_parameters_occurred() {
341 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
342 |
343 | httpClientMock.onPost("/login")
344 | .withParameter("foo", "bar")
345 | .doReturnStatus(200);
346 |
347 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login"), ofString()));
348 | }
349 |
350 | @Test
351 | void should_allow_different_host_then_default() throws Exception {
352 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
353 |
354 | httpClientMock.onGet("/login").doReturn("login");
355 | httpClientMock.onGet("http://www.google.com").doReturn("google");
356 |
357 | final var login = httpClientMock.send(get("http://localhost/login"), ofString());
358 | final var google = httpClientMock.send(get("http://www.google.com"), ofString());
359 |
360 | MatcherAssert.assertThat(login, HttpResponseMatchers.hasContent("login"));
361 | MatcherAssert.assertThat(google, HttpResponseMatchers.hasContent("google"));
362 | }
363 |
364 | @Test
365 | void response_should_contain_request_headers_body_uri_version() throws IOException, URISyntaxException {
366 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
367 | httpClientMock.onGet("/login")
368 | .doReturn("login")
369 | .withHeader("foo", "bar");
370 | var request = get("http://localhost/login");
371 | var response = httpClientMock.send(request, ofString());
372 | assertThat(response.uri(), Matchers.equalTo(new URI("http://localhost/login")));
373 | assertThat(response.request(), Matchers.equalTo(request));
374 | assertThat(response.body(), Matchers.equalTo("login"));
375 | assertTrue(response.headers().firstValue("foo").isPresent());
376 | assertThat(response.headers().firstValue("foo").get(), Matchers.equalTo("bar"));
377 | assertThat(response.version(), Matchers.equalTo(HttpClient.Version.HTTP_1_1));
378 |
379 | }
380 |
381 |
382 | }
383 |
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/HttpClientResponseBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.io.InputStreamReader;
9 | import java.net.URI;
10 | import java.net.http.HttpResponse.BodyHandlers;
11 | import java.nio.ByteBuffer;
12 | import java.nio.charset.Charset;
13 | import java.nio.charset.StandardCharsets;
14 | import java.util.List;
15 | import java.util.stream.Collectors;
16 |
17 | import static com.pgssoft.httpclient.HttpResponseMatchers.hasContent;
18 | import static com.pgssoft.httpclient.HttpResponseMatchers.hasStatus;
19 | import static java.net.http.HttpRequest.BodyPublishers.noBody;
20 | import static java.net.http.HttpRequest.newBuilder;
21 | import static java.net.http.HttpResponse.BodyHandlers.discarding;
22 | import static java.net.http.HttpResponse.BodyHandlers.ofString;
23 | import static org.hamcrest.MatcherAssert.assertThat;
24 | import static org.hamcrest.Matchers.equalTo;
25 | import static org.hamcrest.Matchers.not;
26 | import static org.junit.jupiter.api.Assertions.*;
27 |
28 | class HttpClientResponseBuilderTest {
29 |
30 | @Test
31 | void should_throw_exception_when_no_rule_matches() {
32 | assertThrows(IllegalStateException.class, () -> {
33 | HttpClientMock httpClientMock = new HttpClientMock();
34 | httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
35 | });
36 | }
37 |
38 | @Test
39 | void should_use_next_action_after_every_call() throws Exception {
40 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
41 |
42 | httpClientMock.onGet("/foo")
43 | .doReturn("first")
44 | .doReturn("second")
45 | .doReturn("third");
46 |
47 | httpClientMock.onGet("/bar")
48 | .doReturn("bar")
49 | .doReturnStatus(300)
50 | .doThrowException(new IOException());
51 |
52 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
53 | final var response2 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
54 | final var response3 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
55 | final var response4 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
56 | final var response5 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
57 |
58 | assertThat(response1, hasContent("first"));
59 | assertThat(response2, hasContent("second"));
60 | assertThat(response3, hasContent("third"));
61 | assertThat(response4, hasContent("third"));
62 | assertThat(response5, hasContent("third"));
63 |
64 | final var bar1 = httpClientMock.send(newBuilder(URI.create("http://localhost/bar")).GET().build(), ofString());
65 | final var bar2 = httpClientMock.send(newBuilder(URI.create("http://localhost/bar")).GET().build(), discarding());
66 | assertThat(bar1, hasContent("bar"));
67 | assertThat(bar2, hasStatus(300));
68 |
69 | assertThrows(IOException.class, () -> httpClientMock.send(newBuilder(URI.create("http://localhost/bar")).GET().build(), ofString()));
70 |
71 | }
72 |
73 | @Test
74 | void should_support_response_in_different_charsets() throws Exception {
75 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
76 |
77 | httpClientMock.onGet("/foo")
78 | .doReturn("first")
79 | .doReturn("second", Charset.forName("UTF-16"))
80 | .doReturn("third", Charset.forName("ASCII"));
81 |
82 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
83 | final var response2 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString(StandardCharsets.UTF_16));
84 | final var response3 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString(StandardCharsets.US_ASCII));
85 |
86 | assertThat(response1, hasContent("first"));
87 | assertThat(response2, hasContent("second"));
88 | assertThat(response3, hasContent("third"));
89 | }
90 |
91 | @Test
92 | void shouldFailToDecodeBodyWhenDifferentCharsetsUsed() throws Exception {
93 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
94 | httpClientMock.onGet("/foo").doReturn("output", StandardCharsets.UTF_16);
95 |
96 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString(StandardCharsets.UTF_8));
97 |
98 | assertThat(response1, not(hasContent("output")));
99 | }
100 |
101 | @Test
102 | void should_support_response_in_body_with_status() throws Exception {
103 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
104 |
105 | httpClientMock.onGet("/foo")
106 | .doReturn("first")
107 | .doReturn(300, "second")
108 | .doReturn(400, "third");
109 |
110 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
111 | final var response2 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
112 | final var response3 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
113 | final var response4 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
114 | final var response5 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString());
115 |
116 | assertThat(response1, hasContent("first"));
117 | assertThat(response1, hasStatus(200));
118 | assertThat(response2, hasContent("second"));
119 | assertThat(response2, hasStatus(300));
120 | assertThat(response3, hasContent("third"));
121 | assertThat(response3, hasStatus(400));
122 | assertThat(response4, hasContent("third"));
123 | assertThat(response4, hasStatus(400));
124 | assertThat(response5, hasContent("third"));
125 | assertThat(response5, hasStatus(400));
126 | }
127 |
128 | @Test
129 | void should_throw_exception_when_throwing_action_matched() {
130 | assertThrows(IOException.class, () -> {
131 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
132 | httpClientMock.onGet("/foo").doThrowException(new IOException());
133 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), discarding());
134 | });
135 | }
136 |
137 | @Test
138 | void should_return_status_corresponding_to_match() throws Exception {
139 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
140 |
141 | httpClientMock.onGet("/login").doReturnStatus(200);
142 | httpClientMock.onGet("/abc").doReturnStatus(404);
143 | httpClientMock.onGet("/error").doReturnStatus(500);
144 |
145 | final var ok = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().build(), discarding());
146 | final var notFound = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/abc")).GET().build(), discarding());
147 | final var error = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/error")).GET().build(), discarding());
148 |
149 | assertThat(ok, hasStatus(200));
150 | assertThat(notFound, hasStatus(404));
151 | assertThat(error, hasStatus(500));
152 | }
153 |
154 | @Test
155 | void should_do_custom_action() throws Exception {
156 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
157 | httpClientMock.onPost("/login").doAction(customAction());
158 |
159 | final var response = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), ofString());
160 |
161 | assertThat(response, hasContent("I am a custom action"));
162 |
163 | }
164 |
165 | @Test
166 | void should_add_header_to_response() throws Exception {
167 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
168 | httpClientMock.onPost("/login")
169 | .doReturn("foo").withHeader("tracking", "123")
170 | .doReturn("foo").withHeader("tracking", "456");
171 |
172 | final var first = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), ofString());
173 | final var second = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), ofString());
174 |
175 | assertThat(first.headers().firstValue("tracking").orElse(null), equalTo("123"));
176 | assertThat(second.headers().firstValue("tracking").orElse(null), equalTo("456"));
177 | }
178 |
179 | @Test
180 | void should_add_status_to_response() throws Exception {
181 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
182 | httpClientMock.onGet("/login")
183 | .doReturn("foo").withStatus(300);
184 |
185 | final var login = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().build(), ofString());
186 |
187 | assertThat(login, hasContent("foo"));
188 | assertThat(login, hasStatus(300));
189 |
190 | }
191 |
192 | @Test
193 | void should_return_json_with_right_header() throws Exception {
194 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
195 | httpClientMock.onGet("/foo")
196 | .doReturnJSON("{foo:1}", Charset.forName("UTF-16"));
197 | httpClientMock.onGet("/bar")
198 | .doReturnJSON("{bar:1}");
199 |
200 | final var foo = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), ofString());
201 | final var bar = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/bar")).GET().build(), ofString());
202 | assertThat(foo, hasContent("{foo:1}"));
203 | assertTrue(foo.headers().firstValue("Content-type").isPresent());
204 | assertThat(foo.headers().firstValue("Content-type").get(), equalTo("application/json; charset=UTF-16"));
205 |
206 | assertThat(bar, hasContent("{bar:1}"));
207 | assertTrue(bar.headers().firstValue("Content-type").isPresent());
208 | assertThat(bar.headers().firstValue("Content-type").get(), equalTo("application/json; charset=UTF-8"));
209 | }
210 |
211 | @Test
212 | void should_return_xml_with_right_header() throws Exception {
213 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
214 | httpClientMock.onGet("/foo")
215 | .doReturnXML("bar", Charset.forName("UTF-16"));
216 | httpClientMock.onGet("/bar")
217 | .doReturnXML("bar");
218 | final var foo = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), ofString());
219 | final var bar = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/bar")).GET().build(), ofString());
220 |
221 | assertThat(foo, hasContent("bar"));
222 | assertThat(foo.headers().firstValue("Content-type").orElse(null), equalTo("application/xml; charset=UTF-16"));
223 |
224 | assertThat(bar, hasContent("bar"));
225 | assertThat(bar.headers().firstValue("Content-type").orElse(null), equalTo("application/xml; charset=UTF-8"));
226 | }
227 |
228 | @Test
229 | void should_allow_to_return_response_without_body() throws Exception {
230 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
231 | httpClientMock.onGet("/login")
232 | .doReturnStatus(204); // no content
233 | var request = newBuilder(URI.create("http://localhost:8080/login")).GET().build();
234 | var response = httpClientMock.send(request, BodyHandlers.ofString());
235 |
236 | assertEquals("", response.body());
237 | }
238 |
239 | @Test
240 | void should_throw_exception_when_body_matcher_is_present_on_post_request() {
241 | assertThrows(IllegalStateException.class, () -> {
242 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
243 | httpClientMock.onPost("/path1")
244 | .withBody(equalTo("Body content"))
245 | .doReturnStatus(200);
246 |
247 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/path2")).GET().build(), discarding());
248 | });
249 | }
250 |
251 | @Test
252 | void shouldRespectBodyHandler() throws Exception {
253 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
254 | // A simple string is passed to indicate what we want to see in the response
255 | httpClientMock.onGet().doReturn("expected\nnewline");
256 |
257 | // A BodyHandler is passed that transforms into a Stream of String based on newline characters
258 | final var response = httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), BodyHandlers.ofLines());
259 |
260 | // We expect out String to be returned in the form that the BodyHandler requires - a Stream of Strings
261 | final List responseList = response.body().collect(Collectors.toList());
262 | assertThat(responseList.size(), equalTo(2));
263 | assertThat(responseList.get(0), equalTo("expected"));
264 | assertThat(responseList.get(1), equalTo("newline"));
265 | }
266 |
267 | @Test
268 | void shouldTransformStringToInputStream() throws Exception {
269 | final String expectedString = "expected";
270 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
271 | httpClientMock.onGet().doReturn(expectedString);
272 |
273 | final var response = httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), BodyHandlers.ofInputStream());
274 |
275 | final InputStream output = response.body();
276 | final String outputString = new BufferedReader(new InputStreamReader(output)).readLine();
277 | assertThat(outputString, equalTo(expectedString));
278 | }
279 |
280 | private Action customAction() {
281 | return r -> r.setBodyBytes(ByteBuffer.wrap("I am a custom action".getBytes()));
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/HttpClientVerifyTest.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.net.URI;
7 | import java.net.http.HttpRequest;
8 |
9 | import static java.net.http.HttpRequest.BodyPublishers.noBody;
10 | import static java.net.http.HttpRequest.newBuilder;
11 | import static java.net.http.HttpResponse.BodyHandlers.discarding;
12 | import static java.net.http.HttpResponse.BodyHandlers.ofString;
13 | import static org.hamcrest.Matchers.containsString;
14 | import static org.hamcrest.Matchers.greaterThanOrEqualTo;
15 |
16 | class HttpClientVerifyTest {
17 |
18 | @Test
19 | void shouldHandleAllHttpMethodsWithURL() throws Exception {
20 |
21 | final HttpClientMock httpClientMock = new HttpClientMock();
22 | httpClientMock.onGet("http://localhost").doReturn("empty");
23 | httpClientMock.onPost("http://localhost").doReturn("empty");
24 | httpClientMock.onPut("http://localhost").doReturn("empty");
25 | httpClientMock.onDelete("http://localhost").doReturn("empty");
26 | httpClientMock.onHead("http://localhost").doReturn("empty");
27 | httpClientMock.onOptions("http://localhost").doReturn("empty");
28 | httpClientMock.onPatch("http://localhost").doReturn("empty");
29 |
30 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding());
31 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding());
32 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding());
33 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(noBody()).build(), discarding());
34 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("HEAD", noBody()).build(), discarding());
35 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("OPTIONS", noBody()).build(), discarding());
36 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("PATCH", noBody()).build(), discarding());
37 |
38 | httpClientMock.verify().get("http://localhost").called();
39 | httpClientMock.verify().post("http://localhost").called();
40 | httpClientMock.verify().delete("http://localhost").called();
41 | httpClientMock.verify().put("http://localhost").called();
42 | httpClientMock.verify().options("http://localhost").called();
43 | httpClientMock.verify().head("http://localhost").called();
44 | httpClientMock.verify().patch("http://localhost").called();
45 | }
46 |
47 | @Test
48 | void shouldHandleAllHttpMethodsWithoutURL() throws Exception {
49 | final HttpClientMock httpClientMock = new HttpClientMock();
50 | httpClientMock.onGet("http://localhost").doReturn("empty");
51 | httpClientMock.onPost("http://localhost").doReturn("empty");
52 | httpClientMock.onPut("http://localhost").doReturn("empty");
53 | httpClientMock.onDelete("http://localhost").doReturn("empty");
54 | httpClientMock.onHead("http://localhost").doReturn("empty");
55 | httpClientMock.onOptions("http://localhost").doReturn("empty");
56 | httpClientMock.onPatch("http://localhost").doReturn("empty");
57 |
58 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding());
59 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding());
60 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding());
61 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(noBody()).build(), discarding());
62 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("HEAD", noBody()).build(), discarding());
63 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("OPTIONS", noBody()).build(), discarding());
64 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("PATCH", noBody()).build(), discarding());
65 |
66 | httpClientMock.verify().get().called();
67 | httpClientMock.verify().post().called();
68 | httpClientMock.verify().delete().called();
69 | httpClientMock.verify().put().called();
70 | httpClientMock.verify().options().called();
71 | httpClientMock.verify().head().called();
72 | httpClientMock.verify().patch().called();
73 | }
74 |
75 | @Test
76 | void shouldCountNumberOfHttpMethodCalls() throws Exception {
77 | final HttpClientMock httpClientMock = new HttpClientMock();
78 |
79 | httpClientMock.onGet("http://localhost").doReturn("empty");
80 | httpClientMock.onPost("http://localhost").doReturn("empty");
81 | httpClientMock.onPost("http://localhost").doReturn("empty");
82 | httpClientMock.onDelete("http://localhost").doReturn("empty");
83 | httpClientMock.onDelete("http://localhost").doReturn("empty");
84 | httpClientMock.onDelete("http://localhost").doReturn("empty");
85 |
86 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding());
87 |
88 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding());
89 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding());
90 |
91 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding());
92 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding());
93 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding());
94 |
95 | httpClientMock.verify().get("http://localhost").called();
96 | httpClientMock.verify().post("http://localhost").called(2);
97 | httpClientMock.verify().delete("http://localhost").called(3);
98 |
99 | httpClientMock.verify().get().called(greaterThanOrEqualTo(1));
100 | httpClientMock.verify().post().called(greaterThanOrEqualTo(1));
101 | httpClientMock.verify().delete().called(greaterThanOrEqualTo(1));
102 | }
103 |
104 | @Test
105 | void shouldCountNumberOfUrlCalls() throws Exception {
106 | final HttpClientMock httpClientMock = new HttpClientMock();
107 | httpClientMock.onGet("http://localhost").doReturn("empty");
108 | httpClientMock.onGet("http://www.google.com").doReturn("empty");
109 | httpClientMock.onGet("http://www.google.com").doReturn("empty");
110 | httpClientMock.onGet("http://example.com").doReturn("empty");
111 | httpClientMock.onGet("http://example.com").doReturn("empty");
112 | httpClientMock.onGet("http://example.com").doReturn("empty");
113 |
114 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding());
115 |
116 | httpClientMock.send(newBuilder(URI.create("http://www.google.com")).GET().build(), discarding());
117 | httpClientMock.send(newBuilder(URI.create("http://www.google.com")).GET().build(), discarding());
118 |
119 | httpClientMock.send(newBuilder(URI.create("http://example.com")).GET().build(), discarding());
120 | httpClientMock.send(newBuilder(URI.create("http://example.com")).GET().build(), discarding());
121 | httpClientMock.send(newBuilder(URI.create("http://example.com")).GET().build(), discarding());
122 |
123 | httpClientMock.verify().get("http://localhost").called();
124 | httpClientMock.verify().get("http://www.google.com").called(2);
125 | httpClientMock.verify().get("http://example.com").called(3);
126 | }
127 |
128 | @Test
129 | void shouldVerifyBodyContent() throws Exception {
130 | final HttpClientMock httpClientMock = new HttpClientMock();
131 | httpClientMock.onPost("http://localhost").withBody(containsString("foo")).doReturn("empty");
132 | httpClientMock.onPost("http://localhost").withBody(containsString("foo")).doReturn("empty");
133 | httpClientMock.onPut("http://localhost").withBody(containsString("bar")).doReturn("empty");
134 | httpClientMock.onPut("http://localhost").withBody(containsString("foo")).doReturn("empty");
135 |
136 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(HttpRequest.BodyPublishers.ofString("foo")).build(), ofString());
137 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(HttpRequest.BodyPublishers.ofString("foo")).build(), ofString());
138 |
139 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(HttpRequest.BodyPublishers.ofString("bar")).build(), ofString());
140 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(HttpRequest.BodyPublishers.ofString("foo")).build(), ofString());
141 |
142 | httpClientMock.verify().post("http://localhost").withBody(containsString("foo")).called(2);
143 | httpClientMock.verify().put("http://localhost").withBody(containsString("bar")).called();
144 | httpClientMock.verify().get("http://localhost").withBody(containsString("foo bar")).notCalled();
145 | }
146 |
147 | @Test
148 | void should_handle_path_with_query_parameter() throws Exception {
149 | final HttpClientMock httpClientMock = new HttpClientMock();
150 | httpClientMock.onPost("http://localhost").withParameter("a", "1").withParameter("b", "2").withParameter("c", "3").doReturn("empty");
151 | httpClientMock.onPost("http://localhost").withParameter("a", "1").withParameter("b", "2").doReturn("empty");
152 | httpClientMock.onPost("http://localhost").withParameter("a", "1").doReturn("empty");
153 |
154 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1&b=2&c=3")).POST(noBody()).build(), discarding());
155 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1&b=2")).POST(noBody()).build(), discarding());
156 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1")).POST(noBody()).build(), discarding());
157 |
158 | httpClientMock.verify().post("http://localhost?d=3").notCalled();
159 | httpClientMock.verify().post("http://localhost?a=3").notCalled();
160 | httpClientMock.verify().post("http://localhost?a=1&b=2&c=3").called(1);
161 | httpClientMock.verify().post("http://localhost?a=1&b=2").called(1);
162 | httpClientMock.verify().post("http://localhost?a=1").called(1);
163 | httpClientMock.verify().post("http://localhost").withParameter("a", "1").called(1);
164 |
165 | httpClientMock.verify().post("http://localhost").notCalled();
166 | }
167 |
168 | @Test
169 | void should_handle_path_with_reference() throws Exception {
170 | final HttpClientMock httpClientMock = new HttpClientMock();
171 | httpClientMock.onPost().withParameter("a", "1").withReference("abc").doReturn("empty");
172 | httpClientMock.onPost().withReference("xyz").doReturn("empty");
173 |
174 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1#abc")).POST(noBody()).build(), discarding());
175 | httpClientMock.send(newBuilder(URI.create("http://localhost#xyz")).POST(noBody()).build(), discarding());
176 |
177 | httpClientMock.verify().post("http://localhost?a=1#abc").called(1);
178 | httpClientMock.verify().post("http://localhost#abc").notCalled();
179 | httpClientMock.verify().post("http://localhost#xyz").called(1);
180 | httpClientMock.verify().post("http://localhost").notCalled();
181 | }
182 |
183 | @Test
184 | void should_throw_exception_when_number_of_calls_is_wrong() {
185 | Assertions.assertThrows(IllegalStateException.class, () -> {
186 | final HttpClientMock httpClientMock = new HttpClientMock();
187 |
188 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1")).POST(noBody()).build(), discarding());
189 |
190 | httpClientMock.verify()
191 | .post("http://localhost?a=1#abc")
192 | .called(2);
193 | });
194 | }
195 |
196 | @Test
197 | void should_allow_different_host_then_default() throws Exception {
198 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost");
199 |
200 | httpClientMock.onGet("/login").doReturn("login");
201 | httpClientMock.onGet("http://www.google.com").doReturn("google");
202 |
203 | httpClientMock.send(newBuilder(URI.create("http://localhost/login")).GET().build(), discarding());
204 | httpClientMock.send(newBuilder(URI.create("http://www.google.com")).GET().build(), discarding());
205 |
206 | httpClientMock.verify().get("/login").called();
207 | httpClientMock.verify().get("http://www.google.com").called();
208 | }
209 |
210 | @Test
211 | void should_check_header() throws Exception {
212 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080");
213 |
214 | httpClientMock.onGet("/login").doReturn("OK");
215 |
216 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Chrome").build(), discarding());
217 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Mozilla").build(), discarding());
218 |
219 | httpClientMock.verify().get("/login").withHeader("User-Agent", "Mozilla").called();
220 | httpClientMock.verify().get("/login").withHeader("User-Agent", "Chrome").called();
221 | httpClientMock.verify().get("/login").withHeader("User-Agent", "IE").notCalled();
222 | }
223 |
224 | @Test
225 | void should_verify_each_part_of_URL_in_separate() throws Exception {
226 | final HttpClientMock httpClientMock = new HttpClientMock();
227 | httpClientMock.onGet("http://localhost:8080/login?foo=bar#ref").doReturn("OK");
228 | httpClientMock.send(TestRequests.get("http://localhost:8080/login?foo=bar#ref"), discarding());
229 |
230 | httpClientMock.verify().get().withHost("localhost").called();
231 | httpClientMock.verify().get().withHost("google").notCalled();
232 | httpClientMock.verify().get().withPath("/login").called();
233 | httpClientMock.verify().get().withPath("/logout").notCalled();
234 | httpClientMock.verify().get().withParameter("foo","bar").called();
235 | httpClientMock.verify().get().withParameter("foo","hoo").notCalled();
236 | httpClientMock.verify().get().withReference("ref").called();
237 | httpClientMock.verify().get().withReference("fer").notCalled();
238 |
239 | }
240 |
241 | @Test
242 | void should_verify_custom_condition() throws Exception {
243 | final HttpClientMock httpClientMock = new HttpClientMock();
244 | httpClientMock.onGet("http://localhost:8080/login?foo=bar#ref").doReturn("OK");
245 | httpClientMock.send(TestRequests.get("http://localhost:8080/login?foo=bar#ref"), discarding());
246 |
247 | httpClientMock.verify().get().with(request -> request.uri().getFragment().length()==3).called();
248 | }
249 |
250 | }
251 |
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/HttpResponseMatchers.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import org.hamcrest.BaseMatcher;
4 | import org.hamcrest.Description;
5 | import org.hamcrest.Matcher;
6 |
7 | import java.net.http.HttpResponse;
8 |
9 | public final class HttpResponseMatchers {
10 |
11 | public static Matcher super HttpResponse> hasStatus(int expectedStatus) {
12 | return new BaseMatcher<>() {
13 | @Override
14 | public boolean matches(Object o) {
15 | if (!(o instanceof HttpResponse)) {
16 | return false;
17 | }
18 | return ((HttpResponse) o).statusCode() == expectedStatus;
19 | }
20 |
21 | @Override
22 | public void describeTo(Description description) {
23 | description.appendValue(expectedStatus);
24 | }
25 | };
26 | }
27 |
28 | public static Matcher super HttpResponse> hasContent(final String content) {
29 | return new BaseMatcher<>() {
30 | @SuppressWarnings("unchecked")
31 | @Override
32 | public boolean matches(Object o) {
33 | if (!(o instanceof HttpResponse)) {
34 | return false;
35 | }
36 |
37 | final HttpResponse response = (HttpResponse) o;
38 | return response.body() != null && response.body().equals(content);
39 | }
40 |
41 | @Override
42 | public void describeTo(Description description) {
43 | description.appendText(content);
44 | }
45 | };
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/TestRequests.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import java.net.URI;
4 | import java.net.http.HttpRequest;
5 |
6 | import static java.net.http.HttpRequest.BodyPublishers.noBody;
7 | import static java.net.http.HttpRequest.newBuilder;
8 |
9 | class TestRequests {
10 |
11 | static public HttpRequest post(String url) {
12 | return newBuilder(URI.create(url)).POST(noBody()).build();
13 | }
14 |
15 | static public HttpRequest get(String url) {
16 | return newBuilder(URI.create(url)).GET().build();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/UrlConditionsTest.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient;
2 |
3 | import com.pgssoft.httpclient.internal.UrlConditions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.junit.jupiter.api.Assertions.assertTrue;
7 |
8 | class UrlConditionsTest {
9 |
10 | private static final int EMPTY_PORT_NUMBER = -1;
11 |
12 | @Test
13 | void parseHost() {
14 | UrlConditions urlConditions = UrlConditions.parse("http://localhost");
15 | assertTrue(urlConditions.getSchemaCondition().matches("http"));
16 | assertTrue(urlConditions.getHostCondition().matches("localhost"));
17 | assertTrue(urlConditions.getPortCondition().matches(EMPTY_PORT_NUMBER));
18 | assertTrue(urlConditions.getReferenceCondition().matches(""));
19 | }
20 |
21 |
22 | @Test
23 | void parseHostWithPort() {
24 | UrlConditions urlConditions = UrlConditions.parse("http://localhost:8080");
25 | assertTrue(urlConditions.getHostCondition().matches("localhost"));
26 | assertTrue(urlConditions.getPortCondition().matches(8080));
27 | }
28 |
29 | @Test
30 | void parseHostAndPath() {
31 | UrlConditions urlConditions = UrlConditions.parse("http://localhost/foo/bar");
32 | assertTrue(urlConditions.getHostCondition().matches("localhost"));
33 | assertTrue(urlConditions.getPathCondition().matches("/foo/bar"));
34 | }
35 |
36 | @Test
37 | void parseHostAndPathAndParameters() {
38 | UrlConditions urlConditions = UrlConditions.parse("http://localhost/foo/bar?a=1&b=2");
39 | assertTrue(urlConditions.getHostCondition().matches("localhost"));
40 | assertTrue(urlConditions.getPathCondition().matches("/foo/bar"));
41 | assertTrue(urlConditions.getParameterConditions().matches("a=1&b=2"));
42 | assertTrue(urlConditions.getParameterConditions().matches("b=2&a=1"));
43 | }
44 |
45 | @Test
46 | void parseHostReference() {
47 | UrlConditions urlConditions = UrlConditions.parse("http://localhost#abc");
48 | assertTrue(urlConditions.getHostCondition().matches("localhost"));
49 | assertTrue(urlConditions.getReferenceCondition().matches("abc"));
50 | }
51 |
52 | }
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/test/java/com/pgssoft/httpclient/internal/UrlParamsTest.java:
--------------------------------------------------------------------------------
1 | package com.pgssoft.httpclient.internal;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.List;
6 |
7 | import static org.junit.jupiter.api.Assertions.*;
8 |
9 | class UrlParamsTest {
10 |
11 | @Test
12 | void shouldParseQueryString() {
13 | UrlParams urlParams = UrlParams.parse("a=1&b=2");
14 | List params = urlParams.getParams();
15 | assertEquals("a", params.get(0).getName());
16 | assertEquals("1", params.get(0).getValues().get(0));
17 | assertEquals("b", params.get(1).getName());
18 | assertEquals("2", params.get(1).getValues().get(0));
19 | assertTrue(urlParams.contains("a"));
20 | assertTrue(urlParams.contains("b"));
21 | assertFalse(urlParams.contains("c"));
22 | }
23 |
24 | @Test
25 | void shouldReturnEmptyListForNull() {
26 | assertEquals(0, UrlParams.parse(null).getParams().size());
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------