/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package org.apache.hc.client5.http.routing;

import java.net.InetAddress;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.RouteInfo.LayerType;
import org.apache.hc.client5.http.RouteInfo.TunnelType;
import org.apache.hc.core5.http.HttpHost;
import org.junit.Assert;
import org.junit.Test;

/**
 * Tests for {@link HttpRoute}.
 */
public class TestHttpRoute {

    // a selection of constants for generating routes
    public final static
        HttpHost TARGET1 = new HttpHost("target1.test.invalid", 80);
    public final static
        HttpHost TARGET2 = new HttpHost("target2.test.invalid", 8080);
    // It is not necessary to have extra targets for https.
    // The 'layered' and 'secure' flags are specified explicitly
    // for routes, they will not be determined from the scheme.

    public final static
        HttpHost PROXY1 = new HttpHost("proxy1.test.invalid");
    public final static
        HttpHost PROXY2 = new HttpHost("proxy2.test.invalid", 1080);
    public final static
        HttpHost PROXY3 = new HttpHost("proxy3.test.invalid", 88);

    public final static InetAddress LOCAL41;
    public final static InetAddress LOCAL42;
    public final static InetAddress LOCAL61;
    public final static InetAddress LOCAL62;

    // need static initializer to deal with exceptions
    static {
        try {
            LOCAL41 = InetAddress.getByAddress(new byte[]{ 127, 0, 0, 1 });
            LOCAL42 = InetAddress.getByAddress(new byte[]{ 127, 0, 0, 2 });

            LOCAL61 = InetAddress.getByAddress(new byte[]{
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
            });
            LOCAL62 = InetAddress.getByAddress(new byte[]{
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
            });

        } catch (final Exception x) {
            throw new ExceptionInInitializerError(x);
        }
    }

    @Test
    public void testCstrFullRoute() {
        // create a route with all arguments and check the details
        final HttpHost[] chain3 = { PROXY1, PROXY2, PROXY3 };

        final HttpRoute route = new HttpRoute(TARGET1, LOCAL41, chain3, false,
                                        TunnelType.PLAIN, LayerType.PLAIN);
        Assert.assertEquals("wrong target",
                     TARGET1, route.getTargetHost());
        Assert.assertEquals("wrong local address",
                     LOCAL41, route.getLocalAddress());
        Assert.assertEquals("wrong proxy host",
                     PROXY1, route.getProxyHost());
        Assert.assertEquals("wrong hop count",
                     4, route.getHopCount());
        Assert.assertEquals("wrong hop 0",
                     PROXY1, route.getHopTarget(0));
        Assert.assertEquals("wrong hop 1",
                     PROXY2, route.getHopTarget(1));
        Assert.assertEquals("wrong hop 2",
                     PROXY3, route.getHopTarget(2));
        Assert.assertEquals("wrong hop 3",
                     TARGET1, route.getHopTarget(3));
        Assert.assertFalse("wrong flag: secured", route.isSecure());
        Assert.assertFalse("wrong flag: tunnelled", route.isTunnelled());
        Assert.assertFalse("wrong flag: layered", route.isLayered());

        final String routestr = route.toString();
        Assert.assertTrue("missing target in toString",
                routestr.contains(TARGET1.getHostName()));
        Assert.assertTrue("missing local address in toString",
                routestr.contains(LOCAL41.toString()));
        Assert.assertTrue("missing proxy 1 in toString",
                routestr.contains(PROXY1.getHostName()));
        Assert.assertTrue("missing proxy 2 in toString",
                routestr.contains(PROXY2.getHostName()));
        Assert.assertTrue("missing proxy 3 in toString",
                routestr.contains(PROXY3.getHostName()));
    }

    @Test
    public void testCstrFullFlags() {
        // tests the flag parameters in the full-blown constructor

        final HttpHost[] chain3 = { PROXY1, PROXY2, PROXY3 };

        final HttpRoute routefff = new HttpRoute
            (TARGET1, LOCAL41, chain3, false,
             TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute routefft = new HttpRoute
            (TARGET1, LOCAL41, chain3, false,
             TunnelType.PLAIN, LayerType.LAYERED);
        final HttpRoute routeftf = new HttpRoute
            (TARGET1, LOCAL41, chain3, false,
             TunnelType.TUNNELLED, LayerType.PLAIN);
        final HttpRoute routeftt = new HttpRoute
            (TARGET1, LOCAL41, chain3, false,
             TunnelType.TUNNELLED, LayerType.LAYERED);
        final HttpRoute routetff = new HttpRoute
            (TARGET1, LOCAL41, chain3, true,
             TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute routetft = new HttpRoute
            (TARGET1, LOCAL41, chain3, true,
             TunnelType.PLAIN, LayerType.LAYERED);
        final HttpRoute routettf = new HttpRoute
            (TARGET1, LOCAL41, chain3, true,
             TunnelType.TUNNELLED, LayerType.PLAIN);
        final HttpRoute routettt = new HttpRoute
            (TARGET1, LOCAL41, chain3, true,
             TunnelType.TUNNELLED, LayerType.LAYERED);

        Assert.assertFalse("routefff.secure", routefff.isSecure());
        Assert.assertFalse("routefff.tunnel", routefff.isTunnelled());
        Assert.assertFalse("routefff.layer" , routefff.isLayered());

        Assert.assertFalse("routefft.secure", routefft.isSecure());
        Assert.assertFalse("routefft.tunnel", routefft.isTunnelled());
        Assert.assertTrue ("routefft.layer" , routefft.isLayered());

        Assert.assertFalse("routeftf.secure", routeftf.isSecure());
        Assert.assertTrue ("routeftf.tunnel", routeftf.isTunnelled());
        Assert.assertFalse("routeftf.layer" , routeftf.isLayered());

        Assert.assertFalse("routeftt.secure", routeftt.isSecure());
        Assert.assertTrue ("routeftt.tunnel", routeftt.isTunnelled());
        Assert.assertTrue ("routeftt.layer" , routeftt.isLayered());

        Assert.assertTrue ("routetff.secure", routetff.isSecure());
        Assert.assertFalse("routetff.tunnel", routetff.isTunnelled());
        Assert.assertFalse("routetff.layer" , routetff.isLayered());

        Assert.assertTrue ("routetft.secure", routetft.isSecure());
        Assert.assertFalse("routetft.tunnel", routetft.isTunnelled());
        Assert.assertTrue ("routetft.layer" , routetft.isLayered());

        Assert.assertTrue ("routettf.secure", routettf.isSecure());
        Assert.assertTrue ("routettf.tunnel", routettf.isTunnelled());
        Assert.assertFalse("routettf.layer" , routettf.isLayered());

        Assert.assertTrue ("routettt.secure", routettt.isSecure());
        Assert.assertTrue ("routettt.tunnel", routettt.isTunnelled());
        Assert.assertTrue ("routettt.layer" , routettt.isLayered());


        final Set<HttpRoute> routes = new HashSet<>();
        routes.add(routefff);
        routes.add(routefft);
        routes.add(routeftf);
        routes.add(routeftt);
        routes.add(routetff);
        routes.add(routetft);
        routes.add(routettf);
        routes.add(routettt);
        Assert.assertEquals("some flagged routes are equal", 8, routes.size());

        // we can't test hashCode in general due to its dependency
        // on InetAddress and HttpHost, but we can check for the flags
        final Set<Integer> routecodes = new HashSet<>();
        routecodes.add(Integer.valueOf(routefff.hashCode()));
        routecodes.add(Integer.valueOf(routefft.hashCode()));
        routecodes.add(Integer.valueOf(routeftf.hashCode()));
        routecodes.add(Integer.valueOf(routeftt.hashCode()));
        routecodes.add(Integer.valueOf(routetff.hashCode()));
        routecodes.add(Integer.valueOf(routetft.hashCode()));
        routecodes.add(Integer.valueOf(routettf.hashCode()));
        routecodes.add(Integer.valueOf(routettt.hashCode()));
        Assert.assertEquals("some flagged routes have same hashCode",
                     8, routecodes.size());

        final Set<String> routestrings = new HashSet<>();
        routestrings.add(routefff.toString());
        routestrings.add(routefft.toString());
        routestrings.add(routeftf.toString());
        routestrings.add(routeftt.toString());
        routestrings.add(routetff.toString());
        routestrings.add(routetft.toString());
        routestrings.add(routettf.toString());
        routestrings.add(routettt.toString());
        Assert.assertEquals("some flagged route.toString() are equal",
                     8, routestrings.size());
    }

    @SuppressWarnings("unused")
    @Test
    public void testInvalidArguments() {
        final HttpHost[] chain1 = { PROXY1 };

        // for reference: this one should succeed
        final HttpRoute route = new HttpRoute(TARGET1, null, chain1, false,
                                        TunnelType.TUNNELLED, LayerType.PLAIN);
        Assert.assertNotNull(route);

        try {
            new HttpRoute(null, null, chain1, false,
                                  TunnelType.TUNNELLED, LayerType.PLAIN);
            Assert.fail("missing target not detected");
        } catch (final NullPointerException iax) {
            // expected
        }

        try {
            new HttpRoute(TARGET1, null, (HttpHost[]) null, false,
                                  TunnelType.TUNNELLED, LayerType.PLAIN);
            Assert.fail("missing proxy for tunnel not detected");
        } catch (final IllegalArgumentException iax) {
            // expected
        }
    }

    @Test
    public void testNullEnums() {

        // tests the default values for the enum parameters
        // also covers the accessors for the enum attributes

        final HttpRoute route = new HttpRoute(TARGET1, null, PROXY1, false,
                                        null, null); // here are defaults

        Assert.assertFalse("default tunnelling", route.isTunnelled());
        Assert.assertEquals("untunnelled", TunnelType.PLAIN, route.getTunnelType());

        Assert.assertFalse("default layering", route.isLayered());
        Assert.assertEquals("unlayered", LayerType.PLAIN, route.getLayerType());
    }

    @Test
    public void testEqualsHashcodeClone() throws CloneNotSupportedException {
        final HttpHost[] chain0 = { };
        final HttpHost[] chain1 = { PROXY1 };
        final HttpHost[] chain3 = { PROXY1, PROXY2, PROXY3 };
        final HttpHost[] chain4 = { PROXY1, PROXY3, PROXY2 };

        // create some identical routes
        final HttpRoute route1a = new HttpRoute(TARGET1, LOCAL41, chain3, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route1b = new HttpRoute(TARGET1, LOCAL41, chain3, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route1c = (HttpRoute) route1a.clone();

        Assert.assertEquals("1a 1a", route1a, route1a);
        Assert.assertEquals("1a 1b", route1a, route1b);
        Assert.assertEquals("1a 1c", route1a, route1c);

        Assert.assertEquals("hashcode 1a", route1a.hashCode(), route1a.hashCode());
        Assert.assertEquals("hashcode 1b", route1a.hashCode(), route1b.hashCode());
        Assert.assertEquals("hashcode 1c", route1a.hashCode(), route1c.hashCode());

        Assert.assertEquals("toString 1a", route1a.toString(), route1a.toString());
        Assert.assertEquals("toString 1b", route1a.toString(), route1b.toString());
        Assert.assertEquals("toString 1c", route1a.toString(), route1c.toString());

        // now create some differing routes
        final HttpRoute route2a = new HttpRoute(TARGET2, LOCAL41, chain3, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2b = new HttpRoute(TARGET1, LOCAL42, chain3, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2c = new HttpRoute(TARGET1, LOCAL61, chain3, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2d = new HttpRoute(TARGET1, null, chain3, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2e = new HttpRoute(TARGET1, LOCAL41, (HttpHost[]) null,
                                          false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2f = new HttpRoute(TARGET1, LOCAL41, chain0, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2g = new HttpRoute(TARGET1, LOCAL41, chain1, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2h = new HttpRoute(TARGET1, LOCAL41, chain4, false,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2i = new HttpRoute(TARGET1, LOCAL41, chain3, true,
                                          TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2j = new HttpRoute(TARGET1, LOCAL41, chain3, false,
                                        TunnelType.TUNNELLED, LayerType.PLAIN);
        final HttpRoute route2k = new HttpRoute(TARGET1, LOCAL41, chain3, false,
                                          TunnelType.PLAIN, LayerType.LAYERED);

        // check a special case first: 2f should be the same as 2e
        Assert.assertEquals("2e 2f", route2e, route2f);
        Assert.assertEquals("hashcode 2e 2f", route2e.hashCode(), route2f.hashCode());
        Assert.assertEquals("toString 2e 2f", route2e.toString(), route2f.toString());

        Assert.assertFalse("1a 2a", route1a.equals(route2a));
        Assert.assertFalse("1a 2b", route1a.equals(route2b));
        Assert.assertFalse("1a 2c", route1a.equals(route2c));
        Assert.assertFalse("1a 2d", route1a.equals(route2d));
        Assert.assertFalse("1a 2e", route1a.equals(route2e));
        Assert.assertFalse("1a 2f", route1a.equals(route2f));
        Assert.assertFalse("1a 2g", route1a.equals(route2g));
        Assert.assertFalse("1a 2h", route1a.equals(route2h));
        Assert.assertFalse("1a 2i", route1a.equals(route2i));
        Assert.assertFalse("1a 2j", route1a.equals(route2j));
        Assert.assertFalse("1a 2k", route1a.equals(route2k));

        // repeat the checks in the other direction
        // there could be problems with detecting null attributes

        Assert.assertFalse("2a 1a", route2a.equals(route1a));
        Assert.assertFalse("2b 1a", route2b.equals(route1a));
        Assert.assertFalse("2c 1a", route2c.equals(route1a));
        Assert.assertFalse("2d 1a", route2d.equals(route1a));
        Assert.assertFalse("2e 1a", route2e.equals(route1a));
        Assert.assertFalse("2f 1a", route2f.equals(route1a));
        Assert.assertFalse("2g 1a", route2g.equals(route1a));
        Assert.assertFalse("2h 1a", route2h.equals(route1a));
        Assert.assertFalse("2i 1a", route2i.equals(route1a));
        Assert.assertFalse("2j 1a", route2j.equals(route1a));
        Assert.assertFalse("2k 1a", route2k.equals(route1a));

        // don't check hashCode, it's not guaranteed to be different

        Assert.assertFalse("toString 1a 2a",
                    route1a.toString().equals(route2a.toString()));
        Assert.assertFalse("toString 1a 2b",
                    route1a.toString().equals(route2b.toString()));
        Assert.assertFalse("toString 1a 2c",
                    route1a.toString().equals(route2c.toString()));
        Assert.assertFalse("toString 1a 2d",
                    route1a.toString().equals(route2d.toString()));
        Assert.assertFalse("toString 1a 2e",
                    route1a.toString().equals(route2e.toString()));
        Assert.assertFalse("toString 1a 2f",
                    route1a.toString().equals(route2f.toString()));
        Assert.assertFalse("toString 1a 2g",
                    route1a.toString().equals(route2g.toString()));
        Assert.assertFalse("toString 1a 2h",
                    route1a.toString().equals(route2h.toString()));
        Assert.assertFalse("toString 1a 2i",
                    route1a.toString().equals(route2i.toString()));
        Assert.assertFalse("toString 1a 2j",
                    route1a.toString().equals(route2j.toString()));
        Assert.assertFalse("toString 1a 2k",
                    route1a.toString().equals(route2k.toString()));

        // now check that all of the routes are different from eachother
        // except for those that aren't :-)
        final Set<HttpRoute> routes = new HashSet<>();
        routes.add(route1a);
        routes.add(route2a);
        routes.add(route2b);
        routes.add(route2c);
        routes.add(route2d);
        routes.add(route2e);
        //routes.add(route2f); // 2f is the same as 2e
        routes.add(route2g);
        routes.add(route2h);
        routes.add(route2i);
        routes.add(route2j);
        routes.add(route2k);
        Assert.assertEquals("some routes are equal", 11, routes.size());

        // and a run of cloning over the set
        final Iterator<HttpRoute> iter = routes.iterator();
        while (iter.hasNext()) {
            final HttpRoute origin = iter.next();
            final HttpRoute cloned = (HttpRoute) origin.clone();
            Assert.assertEquals("clone of " + origin, origin, cloned);
            Assert.assertTrue("clone of " + origin, routes.contains(cloned));
        }

        // and don't forget toString
        final Set<String> routestrings = new HashSet<>();
        routestrings.add(route1a.toString());
        routestrings.add(route2a.toString());
        routestrings.add(route2b.toString());
        routestrings.add(route2c.toString());
        routestrings.add(route2d.toString());
        routestrings.add(route2e.toString());
        //routestrings.add(route2f.toString()); // 2f is the same as 2e
        routestrings.add(route2g.toString());
        routestrings.add(route2h.toString());
        routestrings.add(route2i.toString());
        routestrings.add(route2j.toString());
        routestrings.add(route2k.toString());
        Assert.assertEquals("some route.toString() are equal",
                     11, routestrings.size());

        // finally, compare with nonsense
        Assert.assertFalse("route equals null", route1a.equals(null));
        Assert.assertFalse("route equals string", route1a.equals("route1a"));
    }

    @Test
    public void testHopping() {
        // test getHopCount() and getHopTarget() with different proxy chains
        HttpHost[] proxies = null;
        HttpRoute  route   = new HttpRoute(TARGET1, null, proxies, true,
                                           TunnelType.PLAIN, LayerType.PLAIN);
        Assert.assertEquals("A: hop count", 1, route.getHopCount());
        Assert.assertEquals("A: hop 0", TARGET1, route.getHopTarget(0));
        try {
            final HttpHost beyond = route.getHopTarget(1);
            Assert.fail("A: hop 1 is " + beyond);
        } catch (final IllegalArgumentException iax) {
            // expected
        }
        try {
            final HttpHost before = route.getHopTarget(-1);
            Assert.fail("A: hop -1 is " + before);
        } catch (final IllegalArgumentException iax) {
            // expected
        }


        proxies = new HttpHost[]{ PROXY3 };
        route   = new HttpRoute(TARGET1, LOCAL62, proxies, false,
                                TunnelType.TUNNELLED, LayerType.PLAIN);
        Assert.assertEquals("B: hop count", 2, route.getHopCount());
        Assert.assertEquals("B: hop 0", PROXY3, route.getHopTarget(0));
        Assert.assertEquals("B: hop 1", TARGET1, route.getHopTarget(1));
        try {
            final HttpHost beyond = route.getHopTarget(2);
            Assert.fail("B: hop 2 is " + beyond);
        } catch (final IllegalArgumentException iax) {
            // expected
        }
        try {
            final HttpHost before = route.getHopTarget(-2);
            Assert.fail("B: hop -2 is " + before);
        } catch (final IllegalArgumentException iax) {
            // expected
        }


        proxies = new HttpHost[]{ PROXY3, PROXY1, PROXY2 };
        route   = new HttpRoute(TARGET1, LOCAL42, proxies, false,
                                TunnelType.PLAIN, LayerType.LAYERED);
        Assert.assertEquals("C: hop count", 4, route.getHopCount());
        Assert.assertEquals("C: hop 0", PROXY3 , route.getHopTarget(0));
        Assert.assertEquals("C: hop 1", PROXY1 , route.getHopTarget(1));
        Assert.assertEquals("C: hop 2", PROXY2 , route.getHopTarget(2));
        Assert.assertEquals("C: hop 3", TARGET1, route.getHopTarget(3));
        try {
            final HttpHost beyond = route.getHopTarget(4);
            Assert.fail("C: hop 4 is " + beyond);
        } catch (final IllegalArgumentException iax) {
            // expected
        }
        try {
            final HttpHost before = route.getHopTarget(Integer.MIN_VALUE);
            Assert.fail("C: hop -<min> is " + before);
        } catch (final IllegalArgumentException iax) {
            // expected
        }
    }

    @Test
    public void testCstr1() {
        final HttpRoute route = new HttpRoute(TARGET2);
        final HttpRoute should = new HttpRoute
            (TARGET2, null, (HttpHost[]) null, false,
             TunnelType.PLAIN, LayerType.PLAIN);
        Assert.assertEquals("bad convenience route", route, should);
    }

    @Test
    public void testCstr3() {
        // test convenience constructor with 3 arguments
        HttpRoute route = new HttpRoute(TARGET2, LOCAL61, false);
        HttpRoute should = new HttpRoute
            (TARGET2, LOCAL61, (HttpHost[]) null, false,
             TunnelType.PLAIN, LayerType.PLAIN);
        Assert.assertEquals("bad convenience route 3/insecure", route, should);

        route = new HttpRoute(TARGET2, null, true);
        should = new HttpRoute(TARGET2, null, (HttpHost[]) null, true,
                               TunnelType.PLAIN, LayerType.PLAIN);
        Assert.assertEquals("bad convenience route 3/secure", route, should);
    }

    @SuppressWarnings("unused")
    @Test
    public void testCstr4() {
        // test convenience constructor with 4 arguments
        HttpRoute route = new HttpRoute(TARGET2, null, PROXY2, false);
        HttpRoute should = new HttpRoute
            (TARGET2, null, new HttpHost[]{ PROXY2 }, false,
             TunnelType.PLAIN, LayerType.PLAIN);
        Assert.assertEquals("bad convenience route 4/insecure", route, should);

        route = new HttpRoute(TARGET2, LOCAL42, PROXY1, true);
        should = new HttpRoute
            (TARGET2, LOCAL42, new HttpHost[]{ PROXY1 }, true,
             TunnelType.TUNNELLED, LayerType.LAYERED);
        Assert.assertEquals("bad convenience route 4/secure", route, should);

        // this constructor REQUIRES a proxy to be specified
        try {
            new HttpRoute(TARGET1, LOCAL61, null, false);
            Assert.fail("missing proxy not detected");
        } catch (final NullPointerException iax) {
            // expected
        }
    }

    @Test
    public void testCstr6() {
        // test convenience constructor with 6 arguments
        HttpRoute route = new HttpRoute
            (TARGET2, null, PROXY2, true,
             TunnelType.TUNNELLED, LayerType.PLAIN);
        HttpRoute should = new HttpRoute
            (TARGET2, null, new HttpHost[]{ PROXY2 }, true,
             TunnelType.TUNNELLED, LayerType.PLAIN);
        Assert.assertEquals("bad convenience route 6/proxied", route, should);

        route = new HttpRoute
            (TARGET2, null, (HttpHost) null, true,
             TunnelType.PLAIN, LayerType.LAYERED);
        should = new HttpRoute
            (TARGET2, null, (HttpHost[]) null, true,
             TunnelType.PLAIN, LayerType.LAYERED);
        Assert.assertEquals("bad convenience route 6/direct", route, should);

        // handling of null vs. empty chain is checked in the equals tests
    }

    @Test
    public void testImmutable() throws CloneNotSupportedException {

        final HttpHost[] proxies = new HttpHost[]{ PROXY1, PROXY2, PROXY3 };
        final HttpRoute route1 = new HttpRoute(TARGET1, null, proxies, false,
                                         TunnelType.PLAIN, LayerType.PLAIN);
        final HttpRoute route2 = (HttpRoute) route1.clone();
        final HttpRoute route3 = new HttpRoute(TARGET1, null,
                                         proxies.clone(), false,
                                         TunnelType.PLAIN, LayerType.PLAIN);

        // modify the array that was passed to the constructor of route1
        proxies[1] = PROXY3;
        proxies[2] = PROXY2;

        Assert.assertEquals("route differs from clone", route2, route1);
        Assert.assertEquals("route was modified", route3, route1);
    }

}
