/*
 * Copyright (c) 2002-2017 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Licensed 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.
 */
package org.neo4j.driver.internal;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

import java.net.URI;
import java.util.Arrays;
import java.util.List;

import org.neo4j.driver.internal.cluster.LoadBalancer;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.net.BoltServerAddress;
import org.neo4j.driver.internal.retry.RetryDecision;
import org.neo4j.driver.internal.retry.RetryLogic;
import org.neo4j.driver.internal.retry.RetrySettings;
import org.neo4j.driver.internal.security.SecurityPlan;
import org.neo4j.driver.internal.spi.ConnectionPool;
import org.neo4j.driver.internal.spi.ConnectionProvider;
import org.neo4j.driver.v1.AuthToken;
import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Config;
import org.neo4j.driver.v1.Driver;

import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.neo4j.driver.v1.AccessMode.READ;
import static org.neo4j.driver.v1.Config.defaultConfig;

@RunWith( Parameterized.class )
public class DriverFactoryTest
{
    @Parameter
    public URI uri;

    @Parameters( name = "{0}" )
    public static List<URI> uris()
    {
        return Arrays.asList(
                URI.create( "bolt://localhost:7687" ),
                URI.create( "bolt+routing://localhost:7687" )
        );
    }

    @Test
    public void connectionPoolClosedWhenDriverCreationFails() throws Exception
    {
        ConnectionPool connectionPool = mock( ConnectionPool.class );
        DriverFactory factory = new ThrowingDriverFactory( connectionPool );

        try
        {
            createDriver( factory );
            fail( "Exception expected" );
        }
        catch ( Exception e )
        {
            assertThat( e, instanceOf( UnsupportedOperationException.class ) );
        }
        verify( connectionPool ).close();
    }

    @Test
    public void connectionPoolCloseExceptionIsSupressedWhenDriverCreationFails() throws Exception
    {
        ConnectionPool connectionPool = mock( ConnectionPool.class );
        RuntimeException poolCloseError = new RuntimeException( "Pool close error" );
        doThrow( poolCloseError ).when( connectionPool ).close();

        DriverFactory factory = new ThrowingDriverFactory( connectionPool );

        try
        {
            createDriver( factory );
            fail( "Exception expected" );
        }
        catch ( Exception e )
        {
            assertThat( e, instanceOf( UnsupportedOperationException.class ) );
            assertArrayEquals( new Throwable[]{poolCloseError}, e.getSuppressed() );
        }
        verify( connectionPool ).close();
    }

    @Test
    public void usesStandardSessionFactoryWhenNothingConfigured()
    {
        Config config = Config.defaultConfig();
        SessionFactoryCapturingDriverFactory factory = new SessionFactoryCapturingDriverFactory();

        assertNotNull( createDriver( factory, config ) );

        SessionFactory capturedFactory = factory.capturedSessionFactory;
        assertThat( capturedFactory.newInstance( READ, null ), instanceOf( NetworkSession.class ) );
    }

    @Test
    public void usesLeakLoggingSessionFactoryWhenConfigured()
    {
        Config config = Config.build().withLeakedSessionsLogging().toConfig();
        SessionFactoryCapturingDriverFactory factory = new SessionFactoryCapturingDriverFactory();

        assertNotNull( createDriver( factory, config ) );

        SessionFactory capturedFactory = factory.capturedSessionFactory;
        assertThat( capturedFactory.newInstance( READ, null ), instanceOf( LeakLoggingNetworkSession.class ) );
    }

    private Driver createDriver( DriverFactory driverFactory )
    {
        return createDriver( driverFactory, defaultConfig() );
    }

    private Driver createDriver( DriverFactory driverFactory, Config config )
    {
        AuthToken auth = AuthTokens.none();
        RoutingSettings routingSettings = new RoutingSettings( 42, 42 );
        return driverFactory.newInstance( uri, auth, routingSettings, RetrySettings.DEFAULT, config );
    }

    private static class ThrowingDriverFactory extends DriverFactory
    {
        final ConnectionPool connectionPool;

        ThrowingDriverFactory( ConnectionPool connectionPool )
        {
            this.connectionPool = connectionPool;
        }

        @Override
        protected InternalDriver createDriver( Config config, SecurityPlan securityPlan, SessionFactory sessionFactory )
        {
            throw new UnsupportedOperationException( "Can't create direct driver" );
        }

        @Override
        protected Driver createRoutingDriver( BoltServerAddress address, ConnectionPool connectionPool, Config config,
                RoutingSettings routingSettings, SecurityPlan securityPlan, RetryLogic<RetryDecision> retryLogic )
        {
            throw new UnsupportedOperationException( "Can't create routing driver" );
        }

        @Override
        protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan securityPlan, Config config )
        {
            return connectionPool;
        }
    }

    private static class SessionFactoryCapturingDriverFactory extends DriverFactory
    {
        SessionFactory capturedSessionFactory;

        @Override
        protected InternalDriver createDriver( Config config, SecurityPlan securityPlan, SessionFactory sessionFactory )
        {
            return null;
        }

        @Override
        protected LoadBalancer createLoadBalancer( BoltServerAddress address, ConnectionPool connectionPool,
                Config config, RoutingSettings routingSettings )
        {
            return null;
        }

        @Override
        protected SessionFactory createSessionFactory( ConnectionProvider connectionProvider,
                RetryLogic<RetryDecision> retryLogic, Config config )
        {
            SessionFactory sessionFactory = super.createSessionFactory( connectionProvider, retryLogic, config );
            capturedSessionFactory = sessionFactory;
            return sessionFactory;
        }
    }
}

@@ -1753,54 +1753,8 @@
 ls;%0A
-import static org.junit.Assert.assertNotNull;%0A
 impo
@@ -3957,39 +3957,24 @@
 ();%0A%0A       
- assertNotNull(
  createDrive
@@ -3983,34 +3983,32 @@
  factory, config
- )
  );%0A%0A        Ses
@@ -4445,23 +4445,8 @@
     
- assertNotNull(
  cre
@@ -4471,18 +4471,16 @@
 , config
- )
  );%0A%0A   

Fix DriverFactoryTest