/*
 * $Id$
 * 
 * Firebird Open Source J2EE Connector - JDBC Driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a CVS history command.
 *
 * All rights reserved.
 */
package org.firebirdsql.ds;

import java.sql.Connection;
import java.sql.SQLException;

import org.firebirdsql.common.FBTestProperties;
import org.firebirdsql.jdbc.FBSQLException;
import org.firebirdsql.jdbc.FirebirdConnection;
import org.jmock.Expectations;
import org.jmock.Sequence;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.junit.Assert.*;

/**
 * Tests for {@link PooledConnectionHandler} using jMock.
 * 
 * @author <a href="mailto:[email protected]">Mark Rotteveel</a>
 */
public class TestPooledConnectionHandlerMock {

    @Rule
    public final JUnitRuleMockery context = new JUnitRuleMockery();
    {
        context.setImposteriser(ClassImposteriser.INSTANCE);
        context.setThreadingPolicy(new Synchroniser());
    }

    @Rule
    public final ExpectedException expectedException = ExpectedException.none();

    /**
     * The isClosed() method of PooledConnectionHandler and its proxy should
     * report <code>true</code> after handler close.
     * 
     * @throws SQLException
     */
    @Test
    public void testHandlerClose_IsClosed() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);

        context.checking(new Expectations() {
            {
                connectionHandleReleaseExpectations(this, physicalConnection);
                allowing(pooled).releaseConnectionHandler(handler);
            }
        });

        Connection proxy = handler.getProxy();
        handler.close();
        assertTrue("Closed handler should report isClosed() true", handler.isClosed());
        assertTrue("Proxy of closed handler should report isClosed() true", proxy.isClosed());
    }

    /**
     * The isClosed() method of PooledConnectionHandler and its proxy should
     * report <code>true</code> after proxy close.
     * 
     * @throws SQLException
     */
    @Test
    public void testProxyClose_IsClosed() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);

        context.checking(new Expectations() {
            {
                connectionHandleReleaseExpectations(this, physicalConnection);
                allowing(pooled).releaseConnectionHandler(handler);
                allowing(pooled).fireConnectionClosed();
            }
        });

        Connection proxy = handler.getProxy();
        proxy.close();
        assertTrue("Handler of closed proxy should report isClosed() true", handler.isClosed());
        assertTrue("Closed proxy should report isClosed() true", proxy.isClosed());
    }

    /**
     * Closing the PooledConnectionHandler should not notify the owner and not
     * close the physical connection.
     * 
     * @throws SQLException
     */
    @Test
    public void testHandlerClose_NoNotify() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);

        context.checking(new Expectations() {
            {
                connectionHandleReleaseExpectations(this, physicalConnection);
                allowing(pooled).releaseConnectionHandler(handler);
                never(physicalConnection).close();
                never(pooled).fireConnectionClosed();
            }
        });

        handler.close();
    }

    /**
     * Closing the Proxy of the PooledConnectionHandler should notify the owner
     * but not close the physical connection.
     * 
     * @throws SQLException
     */
    @Test
    public void testProxyClose_Notify() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);

        context.checking(new Expectations() {
            {
                connectionHandleReleaseExpectations(this, physicalConnection);
                allowing(pooled).releaseConnectionHandler(handler);
                never(physicalConnection).close();
                oneOf(pooled).fireConnectionClosed();
            }
        });

        handler.getProxy().close();
    }

    /**
     * Calling any Connection method (except isClosed() and close()) on a proxy
     * that was closed by closing the handler should throw an SQLException
     * mentioning the connection was forcibly closed; the owner should not be
     * notified of the exception.
     * 
     * TODO: Consider testing for all Connection methods
     * 
     * @throws SQLException
     */
    @Test
    public void testClosedHandler_throwsException() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);

        context.checking(new Expectations() {
            {
                connectionHandleReleaseExpectations(this, physicalConnection);
                allowing(pooled).releaseConnectionHandler(handler);
                allowing(pooled).fireConnectionClosed();
                never(pooled).fireConnectionError(with(any(SQLException.class)));
            }
        });

        Connection proxy = handler.getProxy();
        handler.close();

        expectedException.expect(SQLException.class);
        expectedException.expectMessage(PooledConnectionHandler.FORCIBLY_CLOSED_MESSAGE);

        proxy.clearWarnings();
    }

    /**
     * Calling any Connection method (except isClosed() and close()) on a proxy
     * that was closed itself should throw an SQLException mentioning the
     * connection was closed; the owner should not be notified of the exception.
     * 
     * TODO: Consider testing for all Connection methods
     * 
     * @throws SQLException
     */
    @Test
    public void testClosedProxy_throwsException() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);

        context.checking(new Expectations() {
            {
                connectionHandleReleaseExpectations(this, physicalConnection);
                allowing(pooled).releaseConnectionHandler(handler);
                allowing(pooled).fireConnectionClosed();
                never(pooled).fireConnectionError(with(any(SQLException.class)));
            }
        });

        Connection proxy = handler.getProxy();
        proxy.close();

        expectedException.expect(SQLException.class);
        expectedException.expectMessage(PooledConnectionHandler.CLOSED_MESSAGE);

        proxy.clearWarnings();
    }

    /**
     * Calling a Connection method on an open proxy should notify the owner of
     * the occurrence of an exception.
     * 
     * @throws SQLException
     */
    @Test
    public void testException_Notify() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);
        final Sequence exceptionSequence = context.sequence("exceptionSequence");

        context.checking(new Expectations() {
            {
                SQLException sqle = new FBSQLException("Mock Exception");
                oneOf(physicalConnection).clearWarnings();
                will(throwException(sqle));
                inSequence(exceptionSequence);
                oneOf(pooled).fireConnectionError(sqle);
                inSequence(exceptionSequence);

            }
        });

        Connection proxy = handler.getProxy();

        expectedException.expect(SQLException.class);

        proxy.clearWarnings();
    }

    /**
     * Closing a proxy should rollback the physical connection if not in
     * auto-commit.
     * 
     * @throws SQLException
     */
    @Test
    public void testCloseNotAutoCommit_rollback() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);
        final Sequence closeSequence = context.sequence("closeSequence");

        context.checking(new Expectations() {
            {
                oneOf(physicalConnection).getAutoCommit();
                will(returnValue(false));
                inSequence(closeSequence);
                oneOf(physicalConnection).rollback();
                inSequence(closeSequence);
                allowing(physicalConnection).clearWarnings();
                allowing(pooled);
            }
        });

        Connection proxy = handler.getProxy();
        proxy.close();
    }

    /**
     * Calling close() on a closed proxy should not throw an exception.
     * 
     * @throws SQLException
     */
    @Test
    public void testDoubleClose_allowed() throws SQLException {
        final FirebirdConnection physicalConnection = context.mock(FirebirdConnection.class);
        final FBPooledConnection pooled = context.mock(FBPooledConnection.class);
        final PooledConnectionHandler handler = new PooledConnectionHandler(physicalConnection,
                pooled);

        context.checking(new Expectations() {
            {
                ignoring(physicalConnection);
                ignoring(pooled);
            }
        });

        Connection proxy = handler.getProxy();
        proxy.close();
        // Expectation: no exception for double close
        proxy.close();
    }

    private void connectionHandleReleaseExpectations(Expectations expectations, FirebirdConnection physicalConnection)
            throws SQLException {
        expectations.allowing(physicalConnection).getAutoCommit();
        expectations.will(Expectations.returnValue(true));
        expectations.allowing(physicalConnection).isWrapperFor(FirebirdConnection.class);
        expectations.will(Expectations.returnValue(true));
        expectations.allowing(physicalConnection).unwrap(FirebirdConnection.class);
        expectations.will(Expectations.returnValue(physicalConnection));
        expectations.allowing(physicalConnection).isUseFirebirdAutoCommit();
        expectations.will(Expectations.returnValue(FBTestProperties.USE_FIREBIRD_AUTOCOMMIT));
        if (FBTestProperties.USE_FIREBIRD_AUTOCOMMIT) {
            expectations.oneOf(physicalConnection).setAutoCommit(false);
            expectations.oneOf(physicalConnection).setAutoCommit(true);
        }
        expectations.allowing(physicalConnection).clearWarnings();
    }
}