View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.rest.client;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.common.utils.ThrowableUtils.*;
21  import static org.apache.juneau.http.HttpHeaders.*;
22  import static org.apache.juneau.http.HttpResponses.*;
23  import static org.junit.jupiter.api.Assertions.*;
24  
25  import java.io.*;
26  import java.util.concurrent.*;
27  import java.util.logging.*;
28  
29  import org.apache.http.*;
30  import org.apache.http.protocol.*;
31  import org.apache.juneau.*;
32  import org.apache.juneau.annotation.*;
33  import org.apache.juneau.http.annotation.*;
34  import org.apache.juneau.http.annotation.Header;
35  import org.apache.juneau.http.response.*;
36  import org.apache.juneau.httppart.*;
37  import org.apache.juneau.json.*;
38  import org.apache.juneau.marshaller.*;
39  import org.apache.juneau.rest.annotation.*;
40  import org.apache.juneau.rest.httppart.*;
41  import org.apache.juneau.rest.mock.*;
42  import org.apache.juneau.rest.servlet.*;
43  import org.apache.juneau.utest.utils.*;
44  import org.apache.juneau.xml.*;
45  import org.junit.jupiter.api.*;
46  
47  class RestClient_Config_RestClient_Test extends TestBase {
48  
49  	public static class ABean {
50  		public int f;
51  		static ABean get() {
52  			var x = new ABean();
53  			x.f = 1;
54  			return x;
55  		}
56  		@Override
57  		public String toString() {
58  			return Json5.of(this);
59  		}
60  	}
61  
62  	private static ABean bean = ABean.get();
63  
64  	@Rest
65  	public static class A extends BasicRestObject {
66  		@RestOp(path="/bean")
67  		public ABean getBean() {
68  			return bean;
69  		}
70  		@RestOp(path="/bean")
71  		public ABean postBean(@Content ABean b) {
72  			return b;
73  		}
74  		@RestOp(path="/echo/*")
75  		public String getEcho(org.apache.juneau.rest.RestRequest req) {
76  			return req.toString();
77  		}
78  		@RestOp(path="/echoBody")
79  		public Reader postEchoBody(org.apache.juneau.rest.RestRequest req) throws IOException {
80  			return req.getContent().getReader();
81  		}
82  		@RestOp(path="/ok")
83  		public Ok getOk() {
84  			return OK;
85  		}
86  		@RestOp(path="/checkHeader")
87  		public String[] getHeader(org.apache.juneau.rest.RestRequest req) {
88  			return req.getHeaders().getAll(req.getHeaderParam("Check").orElse(null)).stream().map(RequestHeader::getValue).toArray(String[]::new);
89  		}
90  	}
91  
92  	public static class A1 extends BasicRestCallHandler {
93  		public A1(RestClient client) {
94  			super(client);
95  		}
96  		@Override
97  		public HttpResponse run(HttpHost target, HttpRequest request, HttpContext context) throws IOException {
98  			request.addHeader("Check","Foo");
99  			request.addHeader("Foo","baz");
100 			return super.run(target,request,context);
101 		}
102 	}
103 
104 	@Test void a01_callHandler() throws Exception {
105 		client().callHandler(A1.class).header("Foo","f1").build().get("/checkHeader").header("Foo","f2").run().assertContent("['f1','f2','baz']");
106 	}
107 
108 	@Test void a02_errorCodes() {
109 		var x1 = client().errorCodes(x -> x == 200).build();
110 		var x2 = client().build();
111 		assertEquals(200, ((RestCallException)assertThrows(Throwable.class, ()->x1.get("/echo").run())).getResponseCode());
112 		assertEquals(200, ((RestCallException)assertThrows(Throwable.class, ()->x2.get("/echo").errorCodes(x -> x == 200).run())).getResponseCode());
113 	}
114 
115 	@Test void a03_executorService() throws Exception {
116 		var es = new ThreadPoolExecutor(1,1,30,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
117 		var x1 = client().executorService(es,true).build();
118 
119 		assertEquals(es,x1.getExecutorService());
120 		x1.get("/echo").runFuture().get().assertStatus(200).assertContent().isContains("GET /echo HTTP/1.1");
121 
122 		es = null;
123 		var x2 = client().executorService(es,true).build();
124 		assertNotNull(x2.getExecutorService());
125 		x2.get("/echo").runFuture().get().assertStatus(200).assertContent().isContains("GET /echo HTTP/1.1");
126 	}
127 
128 	@Test void a04_keepHttpClientOpen() throws Exception {
129 		var x = client().keepHttpClientOpen().build();
130 
131 		var c = x.httpClient;
132 		x.close();
133 		client().httpClient(c).build().get("/ok").runFuture().get().assertContent().isContains("OK");
134 
135 		x = client().keepHttpClientOpen().build();
136 		c = x.httpClient;
137 		x.close();
138 		client().httpClient(c).build().get("/ok").runFuture().get().assertContent().isContains("OK");
139 	}
140 
141 	public static class A5 extends BasicRestCallInterceptor {
142 		public static int x;
143 		@Override
144 		public void onInit(RestRequest req) throws Exception {
145 			x = 1;
146 			req.header("Foo","f2");
147 		}
148 		@Override
149 		public void onConnect(RestRequest req, RestResponse res) throws Exception {
150 			x += 10;
151 			res.addHeader("Bar","b1");
152 		}
153 		@Override
154 		public void onClose(RestRequest req, RestResponse res) throws Exception {
155 			x += 100;
156 		}
157 	}
158 
159 	public static class A5a extends BasicRestCallInterceptor {
160 		@Override
161 		public void onInit(RestRequest req) throws Exception {
162 			throw new RuntimeException("foo");
163 		}
164 	}
165 
166 	public static class A5b extends BasicRestCallInterceptor {
167 		@Override
168 		public void onInit(RestRequest req) throws Exception {}  // NOSONAR
169 		@Override
170 		public void onConnect(RestRequest req, RestResponse res) throws Exception {
171 			throw new RuntimeException("foo");
172 		}
173 		@Override
174 		public void onClose(RestRequest req, RestResponse res) throws Exception {}  // NOSONAR
175 	}
176 
177 	public static class A5c extends BasicRestCallInterceptor {
178 		@Override
179 		public void onInit(RestRequest req) throws Exception {}  // NOSONAR
180 		@Override
181 		public void onConnect(RestRequest req, RestResponse res) throws Exception {}  // NOSONAR
182 		@Override
183 		public void onClose(RestRequest req, RestResponse res) throws Exception {
184 			throw new RuntimeException("foo");
185 		}
186 	}
187 
188 	public static class A5d extends BasicRestCallInterceptor {
189 		@Override
190 		public void onInit(RestRequest req) throws Exception {
191 			throw new RestCallException(null,null,"foo");
192 		}
193 	}
194 
195 	public static class A5e extends BasicRestCallInterceptor {
196 		@Override
197 		public void onInit(RestRequest req) throws Exception {}  // NOSONAR
198 		@Override
199 		public void onConnect(RestRequest req, RestResponse res) throws Exception {
200 			throw new RestCallException(null,null,"foo");
201 		}
202 		@Override
203 		public void onClose(RestRequest req, RestResponse res) throws Exception {}  // NOSONAR
204 	}
205 
206 	public static class A5f extends BasicRestCallInterceptor {
207 		@Override
208 		public void onInit(RestRequest req) throws Exception {}  // NOSONAR
209 		@Override
210 		public void onConnect(RestRequest req, RestResponse res) throws Exception {}  // NOSONAR
211 		@Override
212 		public void onClose(RestRequest req, RestResponse res) throws Exception {
213 			throw new RuntimeException("foo");
214 		}
215 	}
216 
217 	public static class A5g extends BasicRestCallInterceptor {
218 		@Override
219 		public void onInit(RestRequest req) throws Exception {
220 			throw new IOException("foo");
221 		}
222 	}
223 
224 	public static class A5h extends BasicRestCallInterceptor {
225 		@Override
226 		public void onInit(RestRequest req) throws Exception {}  // NOSONAR
227 		@Override
228 		public void onConnect(RestRequest req, RestResponse res) throws Exception {
229 			throw new IOException("foo");
230 		}
231 		@Override
232 		public void onClose(RestRequest req, RestResponse res) throws Exception {}  // NOSONAR
233 	}
234 
235 	public static class A5i extends BasicRestCallInterceptor {
236 		@Override
237 		public void onInit(RestRequest req) throws Exception {}  // NOSONAR
238 		@Override
239 		public void onConnect(RestRequest req, RestResponse res) throws Exception {}  // NOSONAR
240 		@Override
241 		public void onClose(RestRequest req, RestResponse res) throws Exception {
242 			throw new RuntimeException("foo");
243 		}
244 	}
245 
246 	@Test void a05_interceptors() throws Exception {
247 		client().header("Foo","f1").interceptors(A5.class).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().assertContent("['f1','f2','f3']").assertHeader("Bar").is("b1");
248 		assertEquals(111,A5.x);
249 
250 		client().header("Foo","f1").interceptors(new A5()).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().assertContent("['f1','f2','f3']").assertHeader("Bar").is("b1");
251 		assertEquals(111,A5.x);
252 
253 		client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5()).header("Check","foo").header("Foo","f3").run().assertContent("['f1','f2','f3']").assertHeader("Bar").is("b1");
254 		assertEquals(111,A5.x);
255 
256 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(A5a.class).build().get("/checkHeader"));
257 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(A5b.class).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run());
258 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(A5c.class).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().close());
259 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5a()).build().get("/checkHeader"));
260 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5b()).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run());
261 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5c()).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().close());
262 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5a()));
263 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5b()).header("Check","foo").header("Foo","f3").run());
264 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5c()).header("Check","foo").header("Foo","f3").run().close());
265 
266 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(A5d.class).build().get("/checkHeader"));
267 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(A5e.class).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run());
268 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(A5f.class).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().close());
269 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5d()).build().get("/checkHeader"));
270 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5e()).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run());
271 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5f()).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().close());
272 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5d()));
273 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5e()).header("Check","foo").header("Foo","f3").run());
274 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5f()).header("Check","foo").header("Foo","f3").run().close());
275 
276 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(A5g.class).build().get("/checkHeader"));
277 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(A5h.class).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run());
278 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(A5i.class).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().close());
279 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5g()).build().get("/checkHeader"));
280 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5h()).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run());
281 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").interceptors(new A5i()).build().get("/checkHeader").header("Check","foo").header("Foo","f3").run().close());
282 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5g()));
283 		assertThrowsWithMessage(RestCallException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5h()).header("Check","foo").header("Foo","f3").run());
284 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().header("Foo","f1").build().get("/checkHeader").interceptors(new A5i()).header("Check","foo").header("Foo","f3").run().close());
285 
286 		assertThrowsWithMessage(ConfigException.class, "Invalid class of type 'java.lang.String' passed to interceptors().", ()->client().interceptors(String.class));
287 		assertThrowsWithMessage(ConfigException.class, "Invalid object of type 'java.lang.String' passed to interceptors().", ()->client().interceptors(""));
288 		client().interceptors((Object)null).header("Foo","f1").build().get("/checkHeader");
289 		client().interceptors((Class<?>)null).header("Foo","f1").build().get("/checkHeader");
290 	}
291 
292 	public static class A6a extends BasicRestCallInterceptor {
293 		@Override /* RestCallInterceptor */
294 		public void onInit(RestRequest req) throws Exception { throw new RuntimeException("foo"); }
295 	}
296 	public static class A6b extends BasicRestCallInterceptor {
297 		@Override /* RestCallInterceptor */
298 		public void onConnect(RestRequest req, RestResponse res) throws Exception { throw new RuntimeException("foo"); }
299 	}
300 	public static class A6c extends BasicRestCallInterceptor {
301 		@Override /* RestCallInterceptor */
302 		public void onClose(RestRequest req, RestResponse res) throws Exception { throw new RuntimeException("foo"); }
303 	}
304 
305 	@Test void a06_interceptors_exceptionHandling() {
306 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().interceptors(A6a.class).build().post("/bean",bean).complete());
307 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().interceptors(A6b.class).build().post("/bean",bean).complete());
308 		assertThrowsWithMessage(RuntimeException.class, "foo", ()->client().interceptors(A6c.class).build().post("/bean",bean).complete());
309 	}
310 
311 	public static class A7 extends RestClient {
312 		private static String lastMessage;
313 		public A7(RestClient.Builder builder) {
314 			super(builder);
315 		}
316 		@Override
317 		public void log(Level level,String msg,Object...args) {
318 			lastMessage = msg;
319 		}
320 	}
321 
322 	@Test void a07_leakDetection() throws Throwable {
323 		client().detectLeaks().build(A7.class).finalize();
324 		assertEquals("WARNING:  RestClient garbage collected before it was finalized.",A7.lastMessage);
325 	}
326 
327 	@Test void a08_marshaller() throws Exception {
328 		var rc = MockRestClient.create(A.class).marshaller(Xml.DEFAULT).build();
329 		var b = rc.post("/echoBody",bean).run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
330 		assertEquals(json(bean), json(b));
331 	}
332 
333 	@Test void a09_marshalls() throws Exception {
334 		var x = MockRestClient.create(A.class).marshallers(Xml.DEFAULT,Json.DEFAULT).build();
335 
336 		assertThrowsWithMessage(RestCallException.class, "Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.", ()->x.post("/echoBody",bean).run());
337 
338 		assertThrowsWithMessage(Exception.class, "Content-Type not specified in response header.  Cannot find appropriate parser.", ()->x.post("/echoBody",bean).contentType("text/json").run().getContent().as(ABean.class));
339 
340 		var b = x.post("/echoBody",bean).accept("text/xml").contentType("text/xml").run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
341 		assertEquals(json(bean), json(b));
342 
343 		b = x.post("/echoBody",bean).mediaType("text/xml").run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
344 		assertEquals(json(bean), json(b));
345 
346 		b = x.post("/echoBody",bean).accept("text/json").contentType("text/json").run().cacheContent().assertContent("{\"f\":1}").getContent().as(ABean.class);
347 		assertEquals(json(bean), json(b));
348 
349 		b = x.post("/echoBody",bean).mediaType("text/json").run().cacheContent().assertContent("{\"f\":1}").getContent().as(ABean.class);
350 		assertEquals(json(bean), json(b));
351 	}
352 
353 	@Test void a10_serializer_parser() throws Exception {
354 		var x = MockRestClient.create(A.class).serializer(XmlSerializer.class).parser(XmlParser.class).build();
355 
356 		var b = x.post("/echoBody",bean).run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
357 		assertEquals(json(bean), json(b));
358 
359 		x = MockRestClient.create(A.class).serializer(XmlSerializer.DEFAULT).parser(XmlParser.DEFAULT).build();
360 		b = x.post("/echoBody",bean).run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
361 		assertEquals(json(bean), json(b));
362 	}
363 
364 	@Test void a11_serializers_parsers() throws Exception {
365 		var x = MockRestClient.create(A.class).serializers(a(XmlSerializer.class,JsonSerializer.class)).parsers(a(XmlParser.class,JsonParser.class)).build();
366 
367 		assertThrowsWithMessage(RestCallException.class, "Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.", ()->x.post("/echoBody",bean).run());
368 
369 		assertThrowsWithMessage(Exception.class, "Content-Type not specified in response header.  Cannot find appropriate parser.", ()->x.post("/echoBody",bean).contentType("text/json").run().getContent().as(ABean.class));
370 
371 		var b = x.post("/echoBody",bean).accept("text/xml").contentType("text/xml").run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
372 		assertEquals(json(bean), json(b));
373 
374 		b = x.post("/echoBody",bean).mediaType("text/xml").run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
375 		assertEquals(json(bean), json(b));
376 
377 		b = x.post("/echoBody",bean).accept("text/json").contentType("text/json").run().cacheContent().assertContent("{\"f\":1}").getContent().as(ABean.class);
378 		assertEquals(json(bean), json(b));
379 
380 		b = x.post("/echoBody",bean).mediaType("text/json").run().cacheContent().assertContent("{\"f\":1}").getContent().as(ABean.class);
381 		assertEquals(json(bean), json(b));
382 
383 		var x2 = MockRestClient.create(A.class).serializers(XmlSerializer.DEFAULT,JsonSerializer.DEFAULT).parsers(XmlParser.DEFAULT,JsonParser.DEFAULT).build();
384 
385 		assertThrowsWithMessage(RestCallException.class, "Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.", ()->x2.post("/echoBody",bean).run());
386 
387 		assertThrowsWithMessage(Exception.class, "Content-Type not specified in response header.  Cannot find appropriate parser.", ()->x2.post("/echoBody",bean).contentType("text/json").run().getContent().as(ABean.class));
388 
389 		b = x2.post("/echoBody",bean).accept("text/xml").contentType("text/xml").run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
390 		assertEquals(json(bean), json(b));
391 
392 		b = x2.post("/echoBody",bean).mediaType("text/xml").run().cacheContent().assertContent("<object><f>1</f></object>").getContent().as(ABean.class);
393 		assertEquals(json(bean), json(b));
394 
395 		b = x2.post("/echoBody",bean).accept("text/json").contentType("text/json").run().cacheContent().assertContent("{\"f\":1}").getContent().as(ABean.class);
396 		assertEquals(json(bean), json(b));
397 
398 		b = x2.post("/echoBody",bean).mediaType("text/json").run().cacheContent().assertContent("{\"f\":1}").getContent().as(ABean.class);
399 		assertEquals(json(bean), json(b));
400 	}
401 
402 	@Rest(partSerializer=A12a.class,partParser=A12b.class)
403 	public static class A12 extends BasicRestObject {
404 		@RestGet(path="/")
405 		public Ok get(@Header(name="Foo") @Schema(cf="multi") ABean[] foo,org.apache.juneau.rest.RestRequest req,org.apache.juneau.rest.RestResponse res) throws Exception {
406 			assertEquals(2,foo.length);
407 			assertList(req.getHeaders().getAll("Foo").stream().map(RequestHeader::getValue).toList(), "x{f:1}", "x{f:1}");
408 			assertEquals("{f:1}",foo[0].toString());
409 			assertEquals("{f:1}",foo[1].toString());
410 			res.setHeader("Foo",bean);
411 			return OK;
412 		}
413 	}
414 
415 	public static class A12a extends FakeWriterSerializer {
416 		public A12a(Builder builder) {
417 			super(builder.partFunction((t,s,o)->"x" + Json5.of(o)));
418 		}
419 	}
420 
421 	public static class A12b extends FakeReaderParser {
422 		public A12b(Builder builder) {
423 			super(builder.partFunction(A12b::in));
424 		}
425 
426 		private static Object in(HttpPartType type, HttpPartSchema schema, String in, ClassMeta<?> c) {
427 			try {
428 				if (c.isInstanceOf(ABean.class))
429 					return Json5.DEFAULT.read(in.substring(1),c);
430 				return SimplePartParser.DEFAULT.parse(type,schema,in,c);
431 			} catch (Exception e) {
432 				throw asRuntimeException(e);
433 			}
434 		}
435 	}
436 
437 	@Test void a12_partSerializer_partParser() throws Exception {
438 		var x = client(A12.class).headers(serializedHeader("Foo", bean)).partSerializer(A12a.class).partParser(A12b.class).build();
439 		var b = x.get("/").header("Foo",bean).run().assertHeader("Foo").is("x{f:1}").getHeader("Foo").as(ABean.class).get();
440 		assertEquals("{f:1}",b.toString());
441 		b = x.get().header("Foo",bean).run().assertHeader("Foo").is("x{f:1}").getHeader("Foo").as(ABean.class).get();
442 		assertEquals("{f:1}",b.toString());
443 
444 		x = client(A12.class).headers(serializedHeader("Foo", bean)).partSerializer(new A12a(FakeWriterSerializer.create())).partParser(new A12b(FakeReaderParser.create())).build();
445 		b = x.get("/").header("Foo",bean).run().assertHeader("Foo").is("x{f:1}").getHeader("Foo").as(ABean.class).get();
446 		assertEquals("{f:1}",b.toString());
447 	}
448 
449 
450 	@Test void a13_toString() {
451 		var s = client().rootUrl("https://foo").build().toString();
452 		assertTrue(s.contains("rootUrl: 'https://foo'"));
453 	}
454 
455 	@Test void a14_request_target() throws Exception {
456 		client().build().get("/bean").target(new HttpHost("localhost")).run().assertContent("{f:1}");
457 	}
458 
459 	@Test void a15_request_context() throws Exception {
460 		client().build().get("/bean").context(new BasicHttpContext()).run().assertContent("{f:1}");
461 	}
462 
463 	@Test void a16_request_uriParts() throws Exception {
464 		var uri = client().build().get().uriScheme("http").uriHost("localhost").uriPort(8080).uriUserInfo("foo:bar").uri("/bean").uriFragment("baz").queryData("foo","bar").run().assertContent("{f:1}").getRequest().getURI();
465 		assertEquals("http://foo:bar@localhost:8080/bean?foo=bar#baz",uri.toString());
466 
467 		uri = client().build().get().uriScheme("http").uriHost("localhost").uriPort(8080).uriUserInfo("foo","bar").uri("/bean").uriFragment("baz").queryData("foo","bar").run().assertContent("{f:1}").getRequest().getURI();
468 		assertEquals("http://foo:bar@localhost:8080/bean?foo=bar#baz",uri.toString());
469 
470 		uri = client().build().get().uri("http://localhost").uri("http://foo:bar@localhost:8080/bean?foo=bar#baz").run().assertContent("{f:1}").getRequest().getURI();
471 		assertEquals("http://foo:bar@localhost:8080/bean?foo=bar#baz",uri.toString());
472 
473 		uri = client().build().get().uri(new java.net.URI(null,null,null,null)).uri(new java.net.URI("http://foo:bar@localhost:8080/bean?foo=bar#baz")).run().assertContent("{f:1}").getRequest().getURI();
474 		assertEquals("http://foo:bar@localhost:8080/bean?foo=bar#baz",uri.toString());
475 	}
476 
477 	//------------------------------------------------------------------------------------------------------------------
478 	// Helper methods.
479 	//------------------------------------------------------------------------------------------------------------------
480 
481 	private static RestClient.Builder client() {
482 		return MockRestClient.create(A.class).json5();
483 	}
484 
485 	private static RestClient.Builder client(Class<?> c) {
486 		return MockRestClient.create(c).json5();
487 	}
488 }