View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.impl.io;
29  
30  import java.net.InetAddress;
31  import java.net.InetSocketAddress;
32  import java.net.Socket;
33  import java.util.concurrent.TimeUnit;
34  
35  import javax.net.ssl.SSLSocket;
36  
37  import org.apache.hc.client5.http.DnsResolver;
38  import org.apache.hc.client5.http.HttpRoute;
39  import org.apache.hc.client5.http.SchemePortResolver;
40  import org.apache.hc.client5.http.config.ConnectionConfig;
41  import org.apache.hc.client5.http.config.TlsConfig;
42  import org.apache.hc.client5.http.io.ConnectionEndpoint;
43  import org.apache.hc.client5.http.io.DetachedSocketFactory;
44  import org.apache.hc.client5.http.io.LeaseRequest;
45  import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
46  import org.apache.hc.client5.http.protocol.HttpClientContext;
47  import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
48  import org.apache.hc.core5.http.HttpHost;
49  import org.apache.hc.core5.http.config.Lookup;
50  import org.apache.hc.core5.http.io.HttpConnectionFactory;
51  import org.apache.hc.core5.http.io.SocketConfig;
52  import org.apache.hc.core5.io.CloseMode;
53  import org.apache.hc.core5.util.TimeValue;
54  import org.apache.hc.core5.util.Timeout;
55  import org.junit.jupiter.api.Assertions;
56  import org.junit.jupiter.api.BeforeEach;
57  import org.junit.jupiter.api.Test;
58  import org.mockito.Mock;
59  import org.mockito.Mockito;
60  import org.mockito.MockitoAnnotations;
61  
62  public class TestBasicHttpClientConnectionManager {
63  
64      @Mock
65      private ManagedHttpClientConnection conn;
66      @Mock
67      private HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
68      @Mock
69      private Lookup<TlsSocketStrategy> tlsSocketStrategyLookup;
70      @Mock
71      private DetachedSocketFactory detachedSocketFactory;
72      @Mock
73      private TlsSocketStrategy tlsSocketStrategy;
74      @Mock
75      private Socket socket;
76      @Mock
77      private SSLSocket upgradedSocket;
78      @Mock
79      private SchemePortResolver schemePortResolver;
80      @Mock
81      private DnsResolver dnsResolver;
82  
83      private BasicHttpClientConnectionManager mgr;
84  
85      @BeforeEach
86      public void setup() throws Exception {
87          MockitoAnnotations.openMocks(this);
88          mgr = new BasicHttpClientConnectionManager(new DefaultHttpClientConnectionOperator(
89                  detachedSocketFactory, schemePortResolver, dnsResolver, tlsSocketStrategyLookup),
90                  connFactory);
91      }
92  
93      @Test
94      public void testLeaseReleaseNonReusable() throws Exception {
95          final HttpHost target = new HttpHost("localhost", 80);
96          final HttpRoute route = new HttpRoute(target);
97  
98          Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
99  
100         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
101         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
102         Assertions.assertNotNull(endpoint1);
103         Assertions.assertFalse(endpoint1.isConnected());
104 
105         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
106 
107         Assertions.assertNull(mgr.getRoute());
108         Assertions.assertNull(mgr.getState());
109 
110         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
111         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
112         Assertions.assertNotNull(conn2);
113         Assertions.assertFalse(conn2.isConnected());
114 
115         Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
116     }
117 
118     @Test
119     public void testLeaseReleaseReusable() throws Exception {
120         final HttpHost target = new HttpHost("somehost", 80);
121         final HttpRoute route = new HttpRoute(target);
122 
123         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
124 
125         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
126         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
127         Assertions.assertNotNull(endpoint1);
128 
129         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
130 
131         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
132 
133         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
134 
135         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
136 
137         Assertions.assertEquals(route, mgr.getRoute());
138         Assertions.assertNull(mgr.getState());
139 
140         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
141         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
142         Assertions.assertNotNull(conn2);
143         Assertions.assertTrue(conn2.isConnected());
144 
145         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
146     }
147 
148     @Test
149     public void testLeaseReleaseReusableWithState() throws Exception {
150         final HttpHost target = new HttpHost("somehost", 80);
151         final HttpRoute route = new HttpRoute(target);
152 
153         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
154 
155         final LeaseRequest connRequest1 = mgr.lease("some-id", route, "some state");
156         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
157         Assertions.assertNotNull(endpoint1);
158 
159         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
160 
161         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
162         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
163 
164         mgr.release(endpoint1, "some other state", TimeValue.ofMilliseconds(10000));
165 
166         Assertions.assertEquals(route, mgr.getRoute());
167         Assertions.assertEquals("some other state", mgr.getState());
168 
169         final LeaseRequest connRequest2 = mgr.lease("some-id", route, "some other state");
170         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
171         Assertions.assertNotNull(conn2);
172         Assertions.assertTrue(conn2.isConnected());
173 
174         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
175     }
176 
177     @Test
178     public void testLeaseDifferentRoute() throws Exception {
179         final HttpHost target1 = new HttpHost("somehost", 80);
180         final HttpRoute route1 = new HttpRoute(target1);
181 
182         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
183 
184         final LeaseRequest connRequest1 = mgr.lease("some-id", route1, null);
185         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
186         Assertions.assertNotNull(endpoint1);
187 
188         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
189 
190         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
191         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
192 
193         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
194 
195         Assertions.assertEquals(route1, mgr.getRoute());
196         Assertions.assertNull(mgr.getState());
197 
198         final HttpHost target2 = new HttpHost("otherhost", 80);
199         final HttpRoute route2 = new HttpRoute(target2);
200         final LeaseRequest connRequest2 = mgr.lease("some-id", route2, null);
201         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
202         Assertions.assertNotNull(conn2);
203         Assertions.assertFalse(conn2.isConnected());
204 
205         Mockito.verify(conn).close(CloseMode.GRACEFUL);
206         Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
207     }
208 
209     @Test
210     public void testLeaseExpired() throws Exception {
211         final HttpHost target = new HttpHost("somehost", 80);
212         final HttpRoute route = new HttpRoute(target);
213 
214         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
215 
216         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
217         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
218         Assertions.assertNotNull(endpoint1);
219 
220         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
221 
222         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
223         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
224 
225         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(10));
226 
227         Assertions.assertEquals(route, mgr.getRoute());
228         Assertions.assertNull(mgr.getState());
229 
230         Thread.sleep(50);
231 
232         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
233         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
234         Assertions.assertNotNull(conn2);
235         Assertions.assertFalse(conn2.isConnected());
236 
237         Mockito.verify(conn).close(CloseMode.GRACEFUL);
238         Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
239     }
240 
241     @Test
242     public void testReleaseInvalidArg() throws Exception {
243         Assertions.assertThrows(NullPointerException.class, () ->
244                 mgr.release(null, null, TimeValue.NEG_ONE_MILLISECOND));
245     }
246 
247     @Test
248     public void testReleaseAnotherConnection() throws Exception {
249         final ConnectionEndpoint wrongCon = Mockito.mock(ConnectionEndpoint.class);
250         Assertions.assertThrows(IllegalStateException.class, () ->
251                 mgr.release(wrongCon, null, TimeValue.NEG_ONE_MILLISECOND));
252     }
253 
254     @Test
255     public void testShutdown() throws Exception {
256         final HttpHost target = new HttpHost("somehost", 80);
257         final HttpRoute route = new HttpRoute(target);
258 
259         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
260 
261         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
262         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
263         Assertions.assertNotNull(endpoint1);
264 
265         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
266 
267         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
268         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
269 
270         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
271 
272         mgr.close();
273 
274         Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
275 
276         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
277         Assertions.assertThrows(IllegalStateException.class, () -> connRequest2.get(Timeout.ZERO_MILLISECONDS));
278 
279         // Should have no effect
280         mgr.closeExpired();
281         mgr.closeIdle(TimeValue.ZERO_MILLISECONDS);
282         mgr.close();
283 
284         Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
285     }
286 
287     @Test
288     public void testCloseExpired() throws Exception {
289         final HttpHost target = new HttpHost("somehost", 80);
290         final HttpRoute route = new HttpRoute(target);
291 
292         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
293 
294         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
295         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
296         Assertions.assertNotNull(endpoint1);
297 
298         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
299 
300         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
301         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
302 
303         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(10));
304 
305         Assertions.assertEquals(route, mgr.getRoute());
306         Assertions.assertNull(mgr.getState());
307 
308         Thread.sleep(50);
309 
310         mgr.closeExpired();
311 
312         Mockito.verify(conn).close(CloseMode.GRACEFUL);
313     }
314 
315     @Test
316     public void testCloseIdle() throws Exception {
317         final HttpHost target = new HttpHost("somehost", 80);
318         final HttpRoute route = new HttpRoute(target);
319 
320         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
321 
322         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
323         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
324         Assertions.assertNotNull(endpoint1);
325 
326         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
327 
328         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
329         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
330 
331         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
332 
333         Assertions.assertEquals(route, mgr.getRoute());
334         Assertions.assertNull(mgr.getState());
335 
336         Thread.sleep(100);
337 
338         mgr.closeIdle(TimeValue.ofMilliseconds(50));
339 
340         Mockito.verify(conn).close(CloseMode.GRACEFUL);
341     }
342 
343     @Test
344     public void testAlreadyLeased() throws Exception {
345         final HttpHost target = new HttpHost("somehost", 80);
346         final HttpRoute route = new HttpRoute(target);
347 
348         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
349 
350         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
351         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
352         Assertions.assertNotNull(endpoint1);
353         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
354 
355         mgr.getConnection(route, null);
356         Assertions.assertThrows(IllegalStateException.class, () ->
357                 mgr.getConnection(route, null));
358     }
359 
360     @Test
361     public void testTargetConnect() throws Exception {
362         final HttpHost target = new HttpHost("https", "somehost", 443);
363         final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
364         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
365         final HttpRoute route = new HttpRoute(target, local, true);
366 
367         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
368 
369         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
370         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
371         Assertions.assertNotNull(endpoint1);
372 
373         final HttpClientContext context = HttpClientContext.create();
374         final SocketConfig sconfig = SocketConfig.custom().build();
375 
376         mgr.setSocketConfig(sconfig);
377 
378         final ConnectionConfig connectionConfig = ConnectionConfig.custom()
379                 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
380                 .build();
381         mgr.setConnectionConfig(connectionConfig);
382         final TlsConfig tlsConfig = TlsConfig.custom()
383                 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
384                 .build();
385         mgr.setTlsConfig(tlsConfig);
386 
387         Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] {remote});
388         Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
389         Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
390 
391         Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
392         Mockito.when(tlsSocketStrategy.upgrade(
393                 Mockito.same(socket),
394                 Mockito.eq("somehost"),
395                 Mockito.eq(8443),
396                 Mockito.any(),
397                 Mockito.any())).thenReturn(upgradedSocket);
398 
399         mgr.connect(endpoint1, null, context);
400 
401         Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost");
402         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target.getSchemeName(), target);
403         Mockito.verify(detachedSocketFactory, Mockito.times(1)).create(null);
404         Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234);
405         Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 443, tlsConfig, context);
406 
407         mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
408 
409         Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost");
410         Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target.getSchemeName(), target);
411         Mockito.verify(detachedSocketFactory, Mockito.times(2)).create(null);
412         Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123);
413         Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 443, tlsConfig, context);
414     }
415 
416     @Test
417     public void testProxyConnectAndUpgrade() throws Exception {
418         final HttpHost target = new HttpHost("https", "somehost", 443);
419         final HttpHost proxy = new HttpHost("someproxy", 8080);
420         final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
421         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
422         final HttpRoute route = new HttpRoute(target, local, proxy, true);
423 
424         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
425 
426         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
427         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
428         Assertions.assertNotNull(endpoint1);
429 
430         final HttpClientContext context = HttpClientContext.create();
431         final SocketConfig sconfig = SocketConfig.custom().build();
432 
433         mgr.setSocketConfig(sconfig);
434 
435         final ConnectionConfig connectionConfig = ConnectionConfig.custom()
436                 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
437                 .build();
438         mgr.setConnectionConfig(connectionConfig);
439         final TlsConfig tlsConfig = TlsConfig.custom()
440                 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
441                 .build();
442         mgr.setTlsConfig(tlsConfig);
443 
444         Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote});
445         Mockito.when(schemePortResolver.resolve(proxy.getSchemeName(), proxy)).thenReturn(8080);
446         Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
447         Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
448         Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
449 
450         mgr.connect(endpoint1, null, context);
451 
452         Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy");
453         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy.getSchemeName(), proxy);
454         Mockito.verify(detachedSocketFactory, Mockito.times(1)).create(null);
455         Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234);
456 
457         Mockito.when(conn.getSocket()).thenReturn(socket);
458 
459         mgr.upgrade(endpoint1, context);
460 
461         Mockito.verify(tlsSocketStrategy, Mockito.times(1)).upgrade(
462                 socket, "somehost", 443, tlsConfig, context);
463     }
464 
465 
466     @Test
467     public void shouldCloseStaleConnectionAndCreateNewOne() throws Exception {
468         final HttpHost target = new HttpHost("somehost", 80);
469         final HttpRoute route = new HttpRoute(target);
470 
471         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
472 
473         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
474         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
475         Assertions.assertNotNull(endpoint1);
476 
477         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
478 
479         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
480         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
481 
482         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
483 
484         Assertions.assertEquals(route, mgr.getRoute());
485         Assertions.assertNull(mgr.getState());
486 
487         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
488         Mockito.when(conn.isStale()).thenReturn(Boolean.TRUE);
489         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
490         Assertions.assertNotNull(conn2);
491         Assertions.assertTrue(conn2.isConnected());
492 
493         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
494     }
495 
496     @Test
497     public void shouldCloseGRACEFULStaleConnection() throws Exception {
498         final HttpHost target = new HttpHost("somehost", 80);
499         final HttpRoute route = new HttpRoute(target);
500 
501         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
502 
503         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
504         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
505         Assertions.assertNotNull(endpoint1);
506 
507         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
508 
509         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
510         Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
511 
512         // Simulate the connection being released with no keep-alive (it should be closed)
513         mgr.release(endpoint1, null, null);
514 
515         // Ensure the connection was closed
516         Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
517 
518         // Now, when a new lease request is made, the connection is stale
519         Mockito.when(conn.isStale()).thenReturn(Boolean.TRUE);
520 
521         // Attempt to lease a new connection
522         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
523         final ConnectionEndpoint endpoint2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
524         Assertions.assertNotNull(endpoint2);
525 
526         // The connection should be closed and a new one created because the old one was stale
527         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
528 
529         // The new connection should be connected
530         Assertions.assertTrue(endpoint2.isConnected());
531     }
532 
533 }