/**
 *  Copyright 2010 Wallace Wadge
 *
 *    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 com.jolbox.bonecp.provider;

import static org.easymock.EasyMock.*;
import static com.jolbox.bonecp.provider.BoneCPConnectionProvider.CONFIG_CONNECTION_DRIVER_CLASS;
import static com.jolbox.bonecp.provider.BoneCPConnectionProvider.CONFIG_CONNECTION_PASSWORD;
import static com.jolbox.bonecp.provider.BoneCPConnectionProvider.CONFIG_CONNECTION_URL;
import static com.jolbox.bonecp.provider.BoneCPConnectionProvider.CONFIG_CONNECTION_USERNAME;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.expectLastCall;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import org.hibernate.HibernateException;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.jolbox.bonecp.BoneCP;
import com.jolbox.bonecp.BoneCPConfig;
import com.jolbox.bonecp.ConnectionHandle;
import com.jolbox.bonecp.MockConnection;
import com.jolbox.bonecp.MockJDBCAnswer;
import com.jolbox.bonecp.MockJDBCDriver;

/** Test case for the Hibernate boneCP connection provider.
 * @author wallacew
 *
 */
public class TestBoneCPConnectionProvider {
	/** Mock handle. */
	private static BoneCP mockPool;
	/** Mock handle. */
	private static ConnectionHandle mockConnection;
	/** Mock handle. */
	private static Properties mockProperties;
	/** Class under test. */
	private static BoneCPConnectionProvider testClass;
	/** hsqldb driver. */
	private static String URL = "jdbc:mock";
	/** hsqldb username. */
	private static String USERNAME = "sa";
	/** hsqldb password. */
	private static String PASSWORD = "";
	/** hsqldb driver. */
	private static String DRIVER = "com.jolbox.bonecp.MockJDBCDriver";
	/** A dummy query for HSQLDB. */
	public static final String TEST_QUERY = "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
	/** Mock driver handler. */
	private MockJDBCDriver driver;
	
	/** Class setup.
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException 
	 */
	@BeforeClass
	public static void setup() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException {
		mockPool = createNiceMock(BoneCP.class);
		mockConnection = createNiceMock(ConnectionHandle.class);
		mockProperties = createNiceMock(Properties.class);
	
	}


	/**
	 * Mock reset.
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 * @throws NoSuchFieldException 
	 * @throws SecurityException 
	 * @throws SQLException 
	 */
	@Before
	public void before() throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException, SQLException{
		testClass = new BoneCPConnectionProvider();

		// wire in our mock
		Field field = testClass.getClass().getDeclaredField("pool");
		field.setAccessible(true);
		field.set(testClass, mockPool);
		
		
		reset(mockPool, mockConnection, mockProperties);
		
		// load the driver.
		this.driver = new MockJDBCDriver(new MockJDBCAnswer() {
			
			public Connection answer() throws SQLException {
				return new MockConnection();
			}
		});
	}

	/**
	 * Cleanup
	 * @throws SQLException
	 */
	@After
	public void deregisterDriver() throws SQLException{
		if (this.driver != null){
			this.driver.unregister();
		}
	}
	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#close()}.
	 */
	@Test
	public void testClose() {
		mockPool.shutdown();
		expectLastCall().once();
		replay(mockPool);
		testClass.close();
		verify(mockPool);
	}

	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#closeConnection(java.sql.Connection)}.
	 * @throws SQLException 
	 */
	@Test
	public void testCloseConnection() throws SQLException {
		mockConnection.close();
		expectLastCall().once();
		replay(mockPool);
		testClass.closeConnection(mockConnection);
		verify(mockPool);
	}

	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#configure(java.util.Properties)}.
	 * @throws NoSuchFieldException 
	 * @throws SecurityException 
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 * @throws NoSuchMethodException 
	 * @throws ClassNotFoundException 
	 * @throws SQLException 
	 */
	@Test
	@SuppressWarnings("deprecation")
	public void testConfigure() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, SQLException {
		
		Class.forName("com.jolbox.bonecp.MockJDBCDriver");
		expect(mockProperties.getProperty("bonecp.statementsCacheSize")).andReturn("40").anyTimes();
		expect(mockProperties.getProperty("bonecp.minConnectionsPerPartition")).andReturn("20").anyTimes();
		expect(mockProperties.getProperty("bonecp.maxConnectionsPerPartition")).andReturn("50").anyTimes(); 
		expect(mockProperties.getProperty("bonecp.acquireIncrement")).andReturn("5").anyTimes();
		expect(mockProperties.getProperty("bonecp.partitionCount")).andReturn("5").anyTimes();
		expect(mockProperties.getProperty("bonecp.releaseHelperThreads")).andReturn("3").anyTimes();
		expect(mockProperties.getProperty("bonecp.idleConnectionTestPeriod")).andReturn("60").anyTimes();
		expect(mockProperties.getProperty("bonecp.idleMaxAge")).andReturn("240").anyTimes();
		expect(mockProperties.getProperty("javax.persistence.jdbc.url")).andReturn(URL).anyTimes();
		expect(mockProperties.getProperty("javax.persistence.jdbc.user")).andReturn(USERNAME).anyTimes();
		expect(mockProperties.getProperty("javax.persistence.jdbc.password")).andReturn(PASSWORD).anyTimes();
		expect(mockProperties.getProperty("javax.persistence.jdbc.driver")).andReturn(DRIVER).anyTimes();
		expect(mockProperties.getProperty("bonecp.connectionHookClassName")).andReturn("com.jolbox.bonecp.provider.CustomHook").anyTimes();
		expect(mockProperties.getProperty("bonecp.connectionTestStatement")).andReturn(TEST_QUERY).anyTimes();
		expect(mockProperties.getProperty("bonecp.initSQL")).andReturn(TEST_QUERY).anyTimes();
		expect(mockProperties.getProperty("bonecp.logStatementsEnabled")).andReturn("true").anyTimes();



		BoneCPConnectionProvider partialTestClass = createMockBuilder(BoneCPConnectionProvider.class).addMockedMethod( 
				BoneCPConnectionProvider.class.getDeclaredMethod("createPool", BoneCPConfig.class)).createMock();
		expect(partialTestClass.createPool((BoneCPConfig)anyObject())).andReturn(mockPool).once();
		
		replay(mockProperties, partialTestClass);
		partialTestClass.configure(mockProperties);

		// fetch the configuration object and check that everything is as we passed
		BoneCPConfig config = partialTestClass.getConfig();

		assertEquals(40, config.getStatementsCacheSize());
//		assertEquals(30, config.getStatementsCachedPerConnection());
		assertEquals(20, config.getMinConnectionsPerPartition());
		assertEquals(50, config.getMaxConnectionsPerPartition());
		assertEquals(5, config.getAcquireIncrement());
		assertEquals(5, config.getPartitionCount());
		assertEquals(3, config.getReleaseHelperThreads());
		assertEquals(60, config.getIdleConnectionTestPeriodInMinutes());
		assertEquals(240, config.getIdleMaxAgeInMinutes()); 
		assertEquals(URL, config.getJdbcUrl());
		assertEquals(USERNAME, config.getUsername());
		assertEquals(PASSWORD, config.getPassword());
		assertEquals(TEST_QUERY, config.getInitSQL());
		assertEquals(true, config.isLogStatementsEnabled());


		verify(mockProperties, partialTestClass);
		reset(mockProperties);
		expect(mockProperties.getProperty(CONFIG_CONNECTION_DRIVER_CLASS)).andReturn(null).anyTimes();
		replay(mockProperties);
		try{
			testClass.configure(mockProperties);
			fail("Should have failed with exception");
		} catch (HibernateException e){ 
			// do nothing
		}

		reset(mockProperties);
		expect(mockProperties.getProperty(CONFIG_CONNECTION_DRIVER_CLASS)).andReturn(DRIVER).anyTimes();
		expect(mockProperties.getProperty(CONFIG_CONNECTION_URL, "JDBC URL NOT SET IN CONFIG")).andReturn("somethinginvalid").anyTimes();
		expect(mockProperties.getProperty(CONFIG_CONNECTION_USERNAME, "username not set")).andReturn(USERNAME).anyTimes();
		expect(mockProperties.getProperty(CONFIG_CONNECTION_PASSWORD, "password not set")).andReturn(PASSWORD).anyTimes();

		replay(mockProperties);
		try{
			testClass.configure(mockProperties);
			fail("Should have failed with exception");
		} catch (HibernateException e){
			// do nothing
		}
		

		verify(mockProperties);
		reset(mockProperties);
		expect(mockProperties.getProperty(CONFIG_CONNECTION_DRIVER_CLASS)).andReturn("somethingbad").anyTimes();
		expect(mockProperties.getProperty(CONFIG_CONNECTION_URL, "JDBC URL NOT SET IN CONFIG")).andReturn("somethinginvalid").anyTimes();
		expect(mockProperties.getProperty(CONFIG_CONNECTION_USERNAME, "username not set")).andReturn(USERNAME).anyTimes();
		expect(mockProperties.getProperty(CONFIG_CONNECTION_PASSWORD, "password not set")).andReturn(PASSWORD).anyTimes();

		replay(mockProperties);
		try{
			testClass.configure(mockProperties);
			fail("Should have failed with exception");
		} catch (HibernateException e){ 
			// do nothing
		}

		testClass.setClassLoader(getClass().getClassLoader());
		testClass.loadClass("java.lang.String");

		testClass.setClassLoader(this.getClass().getClassLoader());
		assertEquals(this.getClass().getClassLoader(), testClass.getClassLoader());
		
		// coverage stuff
		reset(mockProperties);
		expect(mockProperties.getProperty("bonecp.partitionCount")).andReturn("something bad");
		expect(mockProperties.getProperty("bonecp.logStatementsEnabled")).andReturn("something bad");
		expect(mockProperties.getProperty("bonecp.idleMaxAge")).andReturn("something bad");
		replay(mockProperties);
		try{
			testClass.configure(mockProperties);
			fail("Should have failed with exception");
		} catch (HibernateException e){ 
			// do nothing
		}
		
	}

	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#createPool(BoneCPConfig)}.
	 * @throws SQLException 
	 * @throws ClassNotFoundException 
	 * @throws CloneNotSupportedException 
	 */
	@Test
	public void testCreatePool() throws SQLException, ClassNotFoundException, CloneNotSupportedException {
	
		BoneCPConfig mockConfig = createNiceMock(BoneCPConfig.class);
		expect(mockConfig.getPartitionCount()).andReturn(1).anyTimes();
		expect(mockConfig.getMaxConnectionsPerPartition()).andReturn(1).anyTimes();
		expect(mockConfig.getMinConnectionsPerPartition()).andReturn(1).anyTimes();
		expect(mockConfig.getIdleConnectionTestPeriodInMinutes()).andReturn(100L).anyTimes();
		expect(mockConfig.getUsername()).andReturn("somethingbad").anyTimes();
		expect(mockConfig.getPassword()).andReturn("somethingbad").anyTimes();
		expect(mockConfig.getJdbcUrl()).andReturn("invalid").anyTimes();
//		expect(mockConfig.getReleaseHelperThreads()).andReturn(1).once().andReturn(0).anyTimes();
		replay(mockConfig);
		try{
			testClass.createPool(mockConfig);
			fail("Should throw an exception");
		} catch (RuntimeException e){
			// do nothing
		}
		verify(mockConfig);
		
		reset(mockConfig);
		
		Class.forName(DRIVER);
		mockConfig = createNiceMock(BoneCPConfig.class);
		expect(mockConfig.getPartitionCount()).andReturn(1).anyTimes();
		expect(mockConfig.getMaxConnectionsPerPartition()).andReturn(1).anyTimes();
		expect(mockConfig.getMinConnectionsPerPartition()).andReturn(1).anyTimes();
		expect(mockConfig.getIdleConnectionTestPeriodInMinutes()).andReturn(100L).anyTimes();
		expect(mockConfig.getUsername()).andReturn(USERNAME).anyTimes();
		expect(mockConfig.getPassword()).andReturn(PASSWORD).anyTimes();
		expect(mockConfig.getJdbcUrl()).andReturn(URL).anyTimes();
		expect(mockConfig.isLazyInit()).andReturn(false).anyTimes();
		expect(mockConfig.clone()).andReturn(mockConfig).anyTimes();
		 
		replay(mockConfig);
		
		try{
			testClass.createPool(mockConfig);
		} catch (RuntimeException e){
			fail("Should pass: ");
		}
		verify(mockConfig);
		
		
	}

	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#getConnection()}.
	 * @throws SQLException 
	 * @throws NoSuchFieldException 
	 * @throws SecurityException 
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	@Test
	public void testGetConnection() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {

		Field field = testClass.getClass().getDeclaredField("autocommit");
		field.setAccessible(true);
		field.set(testClass, true);


		field = testClass.getClass().getDeclaredField("isolation");
		field.setAccessible(true);
		field.set(testClass, 8);


		expect(mockPool.getConnection()).andReturn(mockConnection).once();
		expect(mockConnection.getAutoCommit()).andReturn(false).once();
		expect(mockConnection.getTransactionIsolation()).andReturn(0).once();
		mockConnection.setTransactionIsolation(8);
		expectLastCall().once();
		replay(mockPool, mockConnection);
		testClass.getConnection();
		verify(mockPool, mockConnection);

	}
	
	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#getConnection()}.
	 * @throws SQLException 
	 * @throws NoSuchFieldException 
	 * @throws SecurityException 
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	@Test
	public void testGetConnectionWithException() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {

		Field field = testClass.getClass().getDeclaredField("isolation");
		field.setAccessible(true);
		field.set(testClass, 8);


		expect(mockPool.getConnection()).andReturn(mockConnection).once();
		expect(mockConnection.getTransactionIsolation()).andThrow(new SQLException()).once();
		mockConnection.close();
		expectLastCall().once();
		replay(mockPool, mockConnection);
		try{
			testClass.getConnection();
			fail("Should throw an exception");
		} catch(SQLException e){
			// do nothing
		}
		verify(mockPool, mockConnection);

	}

	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#getConnection()}.
	 * @throws SQLException 
	 * @throws NoSuchFieldException 
	 * @throws SecurityException 
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	@Test
	public void testGetConnectionWithExceptionEvenInFinally() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {

		Field field = testClass.getClass().getDeclaredField("isolation");
		field.setAccessible(true);
		field.set(testClass, 8);


		expect(mockPool.getConnection()).andReturn(mockConnection).once();
		expect(mockConnection.getTransactionIsolation()).andThrow(new SQLException()).once();
		mockConnection.close();
		expectLastCall().andThrow(new SQLException()).once();
		replay(mockPool, mockConnection);
		try{
			testClass.getConnection();
			fail("Should throw an exception");
		} catch(SQLException e){
			// do nothing
		}
		verify(mockPool, mockConnection);

	}
	/**
	 * Test method for {@link com.jolbox.bonecp.provider.BoneCPConnectionProvider#supportsAggressiveRelease()}.
	 */
	@Test
	public void testSupportsAggressiveRelease() {
		assertFalse(testClass.supportsAggressiveRelease());
	}

}
