#region License

/*
 * Copyright © 2002-2007 the original author or authors.
 *
 * 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.
 */

#endregion

#region Imports

using System;
using System.Data;
using System.Threading;
using NUnit.Framework;
using Rhino.Mocks;
using Spring.Dao;
using Spring.Data.Common;
using Spring.Data.Core;
using Spring.Data.Support;
using Spring.Support;
using Spring.Transaction;
using Spring.Transaction.Support;

#endregion

namespace Spring.Data
{
    /// <summary>
    /// This class contains tests for AdoPlatformTransactionManager
    /// </summary>
    /// <author>Mark Pollack</author>
    [TestFixture]
    public class AdoPlatformTransactionManagerTests
    {
        private MockRepository mocks;

        [SetUp]
        public void Setup()
        {
            mocks = new MockRepository();
        }

        [TearDown]
        public void TearDown()
        {
            Assert.IsTrue(TransactionSynchronizationManager.ResourceDictionary.Count == 0);
            Assert.IsFalse(TransactionSynchronizationManager.SynchronizationActive);
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsFalse(TransactionSynchronizationManager.ActualTransactionActive);
        }

        [Test]
        public void TransactionCommit()
        {
            #region Mock setup
            IDbProvider dbProvider = (IDbProvider) mocks.CreateMock(typeof (IDbProvider));
            IDbConnection connection = (IDbConnection) mocks.CreateMock(typeof (IDbConnection));
            IDbTransaction transaction = (IDbTransaction) mocks.CreateMock(typeof (IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Commit();
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);

            TransactionTemplate tt = new TransactionTemplate(tm);

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            tt.Execute(new TransactionCommitTxCallback(dbProvider));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            mocks.VerifyAll();
        }

        [Test]
        public void TransactionRollback()
        {
            #region Mock Setup

            IDbProvider dbProvider = (IDbProvider) mocks.CreateMock(typeof (IDbProvider));
            IDbConnection connection = (IDbConnection) mocks.CreateMock(typeof (IDbConnection));
            IDbTransaction transaction = (IDbTransaction) mocks.CreateMock(typeof (IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Rollback();
                LastCall.On(transaction).Repeat.Once();

                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            Exception ex = new ArgumentException("test exception");
            try
            {
                tt.Execute(new TransactionRollbackTxCallback(dbProvider, ex));
                Assert.Fail("Should have thrown exception");
            }
            catch (ArgumentException e)
            {
                Assert.AreEqual(ex, e);
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");


            mocks.VerifyAll();
        }

        [Test]
        public void ParticipatingTransactionWithRollbackOnly()
        {
            IDbProvider dbProvider = (IDbProvider) mocks.CreateMock(typeof (IDbProvider));
            IDbConnection connection = (IDbConnection) mocks.CreateMock(typeof (IDbConnection));
            IDbTransaction transaction = (IDbTransaction) mocks.CreateMock(typeof (IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Rollback();
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }
            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            ITransactionStatus ts = tm.GetTransaction(new DefaultTransactionDefinition());
            TestTransactionSynchronization synch =
                new TestTransactionSynchronization(dbProvider, TransactionSynchronizationStatus.Rolledback);
            TransactionSynchronizationManager.RegisterSynchronization(synch);

            bool outerTransactionBoundaryReached = false;
            try
            {
                Assert.IsTrue(ts.IsNewTransaction);
                TransactionTemplate tt = new TransactionTemplate(tm);
                TransactionTemplate tt2 = new TransactionTemplate(tm);
                tt.Execute(new ParticipatingTxWithRollbackOnlyTxCallback(tt2, dbProvider));
                outerTransactionBoundaryReached = true;
                tm.Commit(ts);
                Assert.Fail("Should have thrown UnexpectedRollbackException");
            }
            catch (UnexpectedRollbackException)
            {
                // expected
                if (!outerTransactionBoundaryReached)
                {
                    tm.Rollback(ts);
                }
                Assert.IsTrue(outerTransactionBoundaryReached);
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsFalse(synch.beforeCommitCalled);
            Assert.IsTrue(synch.beforeCompletionCalled);
            Assert.IsFalse(synch.afterCommitCalled);
            Assert.IsTrue(synch.afterCompletionCalled);

            mocks.VerifyAll();
        }

        [Test]
        [Ignore]
        public void ParticipatingTransactionWithTransactionStartedFromSynch()
        {
        }

        [Test]
        [Ignore]
        public void ParticipatingTransactionWithRollbackOnlyAndInnerSynch()
        {
        }

        [Test]
        public void PropagationRequiresNewWithExistingTransaction()
        {
            #region Mock Setup
            IDbProvider dbProvider = (IDbProvider) mocks.CreateMock(typeof (IDbProvider));
            IDbConnection connection = (IDbConnection) mocks.CreateMock(typeof (IDbConnection));
            IDbTransaction transaction = (IDbTransaction) mocks.CreateMock(typeof (IDbTransaction));

            Expect.Call(dbProvider.CreateConnection()).Return(connection).Repeat.Twice();
            connection.Open();
            LastCall.On(connection).Repeat.Twice();
            Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction).Repeat.Twice();
            //standard tx timeout.
            transaction.Rollback();
            LastCall.On(transaction).Repeat.Once();

            transaction.Commit();
            LastCall.On(transaction).Repeat.Once();

            connection.Dispose();
            LastCall.On(connection).Repeat.Twice();

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);

            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.RequiresNew;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            tt.Execute(new PropagationRequiresNewWithExistingTransactionCallback(tt, dbProvider));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        [Test]
        public void PropagationRequiresNewWithExistingTransactionAndUnrelatedDataSource()
        {
            IDbProvider dbProvider = (IDbProvider) mocks.CreateMock(typeof (IDbProvider));
            IDbConnection connection = (IDbConnection) mocks.CreateMock(typeof (IDbConnection));
            IDbTransaction transaction = (IDbTransaction) mocks.CreateMock(typeof (IDbTransaction));

            Expect.Call(dbProvider.CreateConnection()).Return(connection);
            connection.Open();
            LastCall.On(connection).Repeat.Once();
            Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
            transaction.Commit();
            LastCall.On(transaction).Repeat.Once();
            connection.Dispose();

            IDbProvider dbProvider2 = (IDbProvider) mocks.CreateMock(typeof (IDbProvider));
            IDbConnection connection2 = (IDbConnection) mocks.CreateMock(typeof (IDbConnection));
            IDbTransaction transaction2 = (IDbTransaction) mocks.CreateMock(typeof (IDbTransaction));

            Expect.Call(dbProvider2.CreateConnection()).Return(connection2);
            connection2.Open();
            LastCall.On(connection2).Repeat.Once();
            Expect.Call(connection2.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction2);
            transaction2.Rollback();
            LastCall.On(transaction2).Repeat.Once();
            connection2.Dispose();

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.RequiresNew;

            AdoPlatformTransactionManager tm2 = new AdoPlatformTransactionManager(dbProvider2);
            TransactionTemplate tt2 = new TransactionTemplate(tm2);
            tt2.PropagationBehavior = TransactionPropagation.RequiresNew;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider2), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");


            tt.Execute(new PropagationRequiresNewWithExistingTransactionCallback(tt2, dbProvider));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider2), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        [Test]
        public void PropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSource()
        {
            #region Mock Setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));            
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            Expect.Call(dbProvider.CreateConnection()).Return(connection);
            connection.Open();
            LastCall.On(connection).Repeat.Once();
            Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
            transaction.Rollback();
            LastCall.On(transaction).Repeat.Once();
            connection.Dispose();

            IDbProvider dbProvider2 = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection2 = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
           
            Expect.Call(dbProvider2.CreateConnection()).Return(connection2);
            connection2.Open();
            Exception failure = new Exception("can't open connection");
            LastCall.On(connection2).Throw(failure);

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.RequiresNew;

            AdoPlatformTransactionManager tm2 = new AdoPlatformTransactionManager(dbProvider2);
            tm2.TransactionSynchronization = TransactionSynchronizationState.Never;
            TransactionTemplate tt2 = new TransactionTemplate(tm2);
            tt2.PropagationBehavior = TransactionPropagation.RequiresNew;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider2), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            try
            {
                tt.Execute(
                    new PropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSourceCallback(tt2));
                Assert.Fail("Should have thrown CannotCreateTransactionException");
            } catch(CannotCreateTransactionException ex)
            {
                Assert.AreSame(failure, ex.InnerException);
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider2), "Hasn't thread db provider");
            mocks.VerifyAll();
        }

        [Test]
        public void PropagationNotSupportedWithExistingTransaction()
        {
            #region Mock Setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Commit();
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }
            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.RequiresNew;
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            tt.Execute(new PropagationNotSupportedWithExistingTransactionCallback(tt, dbProvider));

            mocks.VerifyAll();
        }

        [Test]
        public void PropagationNeverWithExistingTransaction()
        {
            #region Mock Setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Rollback();
                LastCall.On(transaction).Repeat.Once();

                connection.Dispose();
            }
            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.RequiresNew;
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            try
            {
                tt.Execute(new PropagationNeverWithExistingTransactionCallback(tt));
                Assert.Fail("Should have thrown IllegalTransactionStateException");
            } catch (IllegalTransactionStateException)
            {
                //expected.
            }
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        [Test]
        public void PropagationRequiresNewWithExistingConnection()
        {
            #region Mock Setup

            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            
            Expect.Call(dbProvider.CreateConnection()).Return(connection);
            connection.Open();
            LastCall.On(connection).Repeat.Once();
            connection.Dispose();

            IDbConnection connection2 = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction2 = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            Expect.Call(dbProvider.CreateConnection()).Return(connection2);
            connection2.Open();
            LastCall.On(connection2).Repeat.Once();
            Expect.Call(connection2.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction2);
            transaction2.Commit();
            LastCall.On(transaction2).Repeat.Once();
            connection2.Dispose();

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.Supports;
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            tt.Execute(new PropagationRequiresNewWithExistingConnectionCallback(tt, connection, connection2, dbProvider));


            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        [Test]
        public void TransactionWithIsolation()
        {
            #region Mock setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.Serializable)).Return(transaction);
                //standard tx timeout.
                transaction.Commit();
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.RequiresNew;
            tt.TransactionIsolationLevel = IsolationLevel.Serializable;
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            tt.Execute(new TransactionCommitTxCallback(dbProvider));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        [Test]
        public void TransactionWithLongTimeout()
        {
            DoTransactionWithTimeout(10);            
        }

        [Test]
        public void TransactionWithShortTimeout()
        {
            DoTransactionWithTimeout(1);
        }

        private void DoTransactionWithTimeout(int timeout)
        {
            #region Mock setup

            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));
            IDbCommand command = (IDbCommand) mocks.CreateMock(typeof (IDbCommand));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
               
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                Expect.Call(connection.CreateCommand()).Return(command);
                command.CommandText = "some SQL statement";
                LastCall.On(command).Repeat.Once();
                if (timeout > 1)
                {
                    command.CommandTimeout = (timeout - 1);
                    transaction.Commit();
                } else
                {
                    transaction.Rollback();
                }                
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.TransactionTimeout = timeout;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            try
            {
                tt.Execute(new TransactionWithTimeoutCallback(dbProvider));
                if (timeout <= 1)
                {
                    Assert.Fail("Should have thrown TransactionTimedOutException");
                }
            } catch (TransactionTimedOutException)
            {
                if (timeout <=1 )
                {
                    //expected
                } else
                {
                    throw;
                }
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        [Test]
        public void TransactionWithExceptionOnBegin()
        {
            #region Mock setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));       
            
            // CreateConnection is called in AdoPlatformTransactionManager.DoBegin
            Expect.Call(dbProvider.CreateConnection()).Throw(new TestSqlException("Cannot begin", "314"));     
            

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            try
            {
                tt.Execute(new TransactionDelegate(TransactionWithExceptionNoOp));                
            } catch (CannotCreateTransactionException)
            {
                // expected
            }


            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        private object TransactionWithExceptionNoOp(ITransactionStatus status)
        {
            return null;
        }

        [Test]
        public void TransactionWithExceptionOnCommit()
        {
            #region Mock setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Commit();
                LastCall.On(transaction).Throw(new TestSqlException("Cannot commit", "314"));
                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            
            try
            {
                tt.Execute(new TransactionDelegate(TransactionWithExceptionNoOp));
            } catch (TransactionSystemException)
            {
                //expected
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            mocks.VerifyAll();
            
        }


        [Test]
        public void TransactionWithExceptionOnCommitAndRollbackOnCommitFailure()
        {
            #region Mock Setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                transaction.Commit();
                LastCall.On(transaction).Throw(new TestSqlException("Cannot commit", "314"));

                transaction.Rollback();
                LastCall.On(transaction).Repeat.Once();

                connection.Dispose();
            }
            #endregion
            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            tm.RollbackOnCommitFailure = true;
            TransactionTemplate tt = new TransactionTemplate(tm);

            try
            {
                tt.Execute(new TransactionDelegate(TransactionWithExceptionNoOp));
            }
            catch (TransactionSystemException)
            {
                //expected
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();
        }

        [Test]
        public void TransactionWithExceptionOnRollback()
        {
            #region Mock Setup

            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Rollback();
                LastCall.On(transaction).Throw(new TestSqlException("Cannot commit", "314"));

                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);

            try
            {
                tt.Execute(new TransactionDelegate(TransactionWithExceptionOnRollbackMethod));
            }
            catch (TransactionSystemException)
            {
                //expected
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            mocks.VerifyAll();

        }

        private object TransactionWithExceptionOnRollbackMethod(ITransactionStatus status)
        {
            status.SetRollbackOnly();
            return null;
        }

        [Test]
        public void TransactionWithPropagationSupports()
        {
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.Supports;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            tt.Execute(new TransactionWithPropagationSupportsCallback(dbProvider));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            mocks.VerifyAll();
        }

        [Test]
        public void TransactionWithPropagationNotSupported()
        {
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.NotSupported;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            tt.Execute(new TransactionWithPropagationNotSupportedCallback(dbProvider));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            mocks.VerifyAll();
        }

        [Test]
        public void TransactionWithPropagationNever()
        {
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.Never;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");

            tt.Execute(new TransactionWithPropagationNotSupportedCallback(dbProvider));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            mocks.VerifyAll();
        }

        [Test]
        public void ExistingTransactionWithPropagationNestedNotSupported()
        {
            #region Mock setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                
                transaction.Rollback();
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.Nested;
            
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");

            try
            {
                tt.Execute(new ExistingTransactionWithPropagationNestedCallback(dbProvider, tt));
                Assert.Fail("Should have thrown NestedTransactionNotSupportedException");
            } catch (NestedTransactionNotSupportedException)
            {
                // expected 
            }

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            mocks.VerifyAll();
        }

        [Test]
        public void TransactionWithPropagationNested()
        {
            #region Mock setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);
                //standard tx timeout.
                transaction.Commit();
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.Nested;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");


            tt.Execute(new TransactionDelegate(TransactionWithPropagationNestedMethod));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            mocks.VerifyAll();


        }

        private object TransactionWithPropagationNestedMethod(ITransactionStatus status)
        {
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            return null;
        }

        [Test]
        public void TransactionWithPropagationNestedAndRollback()
        {
            #region Mock setup
            IDbProvider dbProvider = (IDbProvider)mocks.CreateMock(typeof(IDbProvider));
            IDbConnection connection = (IDbConnection)mocks.CreateMock(typeof(IDbConnection));
            IDbTransaction transaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction));

            using (mocks.Ordered())
            {
                Expect.Call(dbProvider.CreateConnection()).Return(connection);
                connection.Open();
                LastCall.On(connection).Repeat.Once();
                Expect.Call(connection.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction);               
                transaction.Rollback();
                LastCall.On(transaction).Repeat.Once();
                connection.Dispose();
            }

            #endregion

            mocks.ReplayAll();

            AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider);
            TransactionTemplate tt = new TransactionTemplate(tm);
            tt.PropagationBehavior = TransactionPropagation.Nested;

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active");


            tt.Execute(new TransactionDelegate(TransactionWithPropagationNestedAndRollbackMethod));

            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider");
            mocks.VerifyAll();


        }

        private object TransactionWithPropagationNestedAndRollbackMethod(ITransactionStatus status)
        {
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            status.SetRollbackOnly();
            return null;
        }
    }

    internal class ExistingTransactionWithPropagationNestedCallback : ITransactionCallback
    {
        private IDbProvider dbProvider;
        private TransactionTemplate tt;

        public ExistingTransactionWithPropagationNestedCallback(IDbProvider dbProvider, TransactionTemplate transactionTemplate)
        {
            this.dbProvider = dbProvider;
            tt = transactionTemplate;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            //TODO: Note no support for savepoints at this time (1.1), so can't check that a savepoint isn't present.
            
            tt.Execute(new ExistingTransactionWithPropagationNestedCallback2(dbProvider));

            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            //TODO: Note no support for savepoints at this time (1.1), so can't check that a savepoint isn't present.
            return null;
        }
    }

    internal class ExistingTransactionWithPropagationNestedCallback2 : ITransactionCallback
    {
        private IDbProvider dbProvider;

        public ExistingTransactionWithPropagationNestedCallback2(IDbProvider provider)
        {
            dbProvider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(TransactionSynchronizationManager.HasResource(dbProvider), "Has thread db provider");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronizations active");
            Assert.IsTrue(!status.IsNewTransaction, "Isn't new transaction");
            //TODO: Note no support for savepoints at this time (1.1), so can't check that a savepoint is present.
            return null;
        }
    }

    #region Supporting class for TransactionWithPropagationNotSupported
    internal class TransactionWithPropagationNotSupportedCallback : ITransactionCallback
    {
        private IDbProvider provider;

        public TransactionWithPropagationNotSupportedCallback(IDbProvider provider)
        {
            this.provider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider");
            Assert.IsTrue(!status.IsNewTransaction, "Is not new transaction");
            return null;
        }
    }
    #endregion

    #region Supporting class for TransactionWithPropagationSupports
    internal class TransactionWithPropagationSupportsCallback : ITransactionCallback
    {
        private IDbProvider provider;

        public TransactionWithPropagationSupportsCallback(IDbProvider provider)
        {
            this.provider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider");
            Assert.IsTrue(!status.IsNewTransaction,"Is not new transaction");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsFalse(TransactionSynchronizationManager.ActualTransactionActive);
            return null;
        }
    }
    #endregion


    #region Supporting class for TransactionWithTimeout

    internal class TransactionWithTimeoutCallback : ITransactionCallback
    {
        private IDbProvider provider;
        public TransactionWithTimeoutCallback(IDbProvider provider)
        {
            this.provider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            try
            {
                Thread.Sleep(1500);
            } catch (Exception)
            {
                
            }
            try
            {
                IDbConnection con = ConnectionUtils.GetConnection(provider);
                IDbCommand cmd = con.CreateCommand();
                cmd.CommandText = "some SQL statement";
                ConnectionUtils.ApplyTransactionTimeout(cmd, provider);

            } catch (Exception e)
            {
                if (e.GetType() != typeof(TransactionTimedOutException))
                {
                    throw new DataAccessResourceFailureException("", e);
                }
                throw;
            }
            return null;
        }
    }
    #endregion

    #region Supporting class for PropagationRequiresNewWithExistingConnection

    internal class PropagationRequiresNewWithExistingConnectionCallback : ITransactionCallback
    {
        private TransactionTemplate tt;
        private IDbProvider dbProvider;
        private IDbConnection connection;
        private IDbConnection connection2;

        public PropagationRequiresNewWithExistingConnectionCallback(TransactionTemplate transactionTemplate, IDbConnection connection, IDbConnection connection2, IDbProvider provider)
        {
            tt = transactionTemplate;
            this.connection = connection;
            this.connection2 = connection2;
            dbProvider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronizations active");
            Assert.AreSame(connection, ConnectionUtils.GetConnection(dbProvider));
            Assert.AreSame(connection, ConnectionUtils.GetConnection(dbProvider));
            tt.PropagationBehavior = TransactionPropagation.RequiresNew;
            tt.ReadOnly = true;
            tt.Execute(new PropagationRequiresNewWithExistingConnectionCallback2(dbProvider, connection2));
            Assert.AreSame(connection, ConnectionUtils.GetConnection(dbProvider));
            return null;
        }
    }

    internal class PropagationRequiresNewWithExistingConnectionCallback2 : ITransactionCallback
    {
        private IDbProvider dbProvider;
        private IDbConnection connection2;


        public PropagationRequiresNewWithExistingConnectionCallback2(IDbProvider dbProvider, IDbConnection connection2)
        {
            this.dbProvider = dbProvider;
            this.connection2 = connection2;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(TransactionSynchronizationManager.HasResource(dbProvider), "Has thread db provider");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronizations active");
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.AreSame(connection2, ConnectionUtils.GetConnection(dbProvider));
            Assert.AreSame(connection2, ConnectionUtils.GetConnection(dbProvider));

            return null;
        }
    }

    #endregion

    #region Supporting classes for PropagationNeverWithExistingTransaction

    internal class PropagationNeverWithExistingTransactionCallback : ITransactionCallback
    {
        private TransactionTemplate innerTxTemplate;

        public PropagationNeverWithExistingTransactionCallback(TransactionTemplate transactionTemplate)
        {
            innerTxTemplate = transactionTemplate;
        }


        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            innerTxTemplate.PropagationBehavior = TransactionPropagation.Never;
            innerTxTemplate.Execute(new PropagationNeverWithExistingTransactionCallback2());
            Assert.Fail("Should have thrown IllegalTransactionStateException");
            return null;
        }        
    }

    internal class PropagationNeverWithExistingTransactionCallback2 : ITransactionCallback
    {

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.Fail("Should have thrown IllegalTransactionStateException");
            return null;
        }
    }

    #endregion

    #region Supporting classes for PropagationNotSupportedWithExistingTransaction

    internal class PropagationNotSupportedWithExistingTransactionCallback : ITransactionCallback
    {
        private TransactionTemplate innerTxTemplate;
        private IDbProvider dbProvider;

        public PropagationNotSupportedWithExistingTransactionCallback(TransactionTemplate transactionTemplate, IDbProvider provider)
        {
            innerTxTemplate = transactionTemplate;
            dbProvider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronization active");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            innerTxTemplate.PropagationBehavior = TransactionPropagation.NotSupported;
            innerTxTemplate.Execute(new PropagationNotSupportedWithExistingTransactionCallback2(dbProvider));
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsTrue(TransactionSynchronizationManager.ActualTransactionActive);
            return null;
        }
    }

    internal class PropagationNotSupportedWithExistingTransactionCallback2 : ITransactionCallback
    {
        private IDbProvider provider;

        public PropagationNotSupportedWithExistingTransactionCallback2(IDbProvider provider)
        {
            this.provider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(!TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive);
            Assert.IsTrue(!status.IsNewTransaction, "Isn't new transaction");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsFalse(TransactionSynchronizationManager.ActualTransactionActive);
            status.SetRollbackOnly();
            return null;
        }
    }

    #endregion

    #region Supporting class for PropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSource

    internal class PropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSourceCallback : ITransactionCallback
    {
        private TransactionTemplate innerTxTemplate;


        public PropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSourceCallback(TransactionTemplate transactionTemplate)
        {
            innerTxTemplate = transactionTemplate;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronization active");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsTrue(TransactionSynchronizationManager.ActualTransactionActive);
            innerTxTemplate.Execute(new PropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSourceCallback2());
            return null;
        }
    }

    internal class PropagationRequiresNewWithExistingTransactionAndUnrelatedFailingDataSourceCallback2 : ITransactionCallback
    {
        public object DoInTransaction(ITransactionStatus status)
        {
            status.SetRollbackOnly();
            return null;
        }
    }

    #endregion

    #region Supporting class for PropagationRequiresNewWithExistingTransaction

    internal class PropagationRequiresNewWithExistingTransactionCallback : ITransactionCallback
    {
        private TransactionTemplate innerTxTemplate;
        private IDbProvider dbProvider;

        public PropagationRequiresNewWithExistingTransactionCallback(TransactionTemplate transactionTemplate,
                                                                     IDbProvider provider)
        {
            innerTxTemplate = transactionTemplate;
            dbProvider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronization active");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsTrue(TransactionSynchronizationManager.ActualTransactionActive);
            innerTxTemplate.Execute(new PropagationRequiresNewWithExistingTransactionCallback2(dbProvider));

            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsTrue(TransactionSynchronizationManager.ActualTransactionActive);
            return null;
        }
    }

    internal class PropagationRequiresNewWithExistingTransactionCallback2 : ITransactionCallback
    {
        private IDbProvider dbProvider;

        public PropagationRequiresNewWithExistingTransactionCallback2(IDbProvider dbProvider)
        {
            this.dbProvider = dbProvider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(TransactionSynchronizationManager.HasResource(dbProvider), "Has thread connection");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronization active");
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            Assert.IsTrue(TransactionSynchronizationManager.ActualTransactionActive);
            status.SetRollbackOnly();
            return null;
        }
    }

    #endregion

    #region Supporting class for TransactionCommit test

    internal class TransactionCommitTxCallback : ITransactionCallback
    {
        private IDbProvider provider;

        public TransactionCommitTxCallback(IDbProvider provider)
        {
            this.provider = provider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(TransactionSynchronizationManager.HasResource(provider), "Has thread db provider");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive);
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            Assert.IsTrue(TransactionSynchronizationManager.ActualTransactionActive);
            Assert.IsFalse(TransactionSynchronizationManager.CurrentTransactionReadOnly);
            return null;
        }
    }

    #endregion

    #region Supporting class for TransactionRollback test

    internal class TransactionRollbackTxCallback : ITransactionCallback
    {
        private IDbProvider provider;
        private Exception exception;

        public TransactionRollbackTxCallback(IDbProvider provider, Exception exception)
        {
            this.provider = provider;
            this.exception = exception;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive);
            Assert.IsTrue(status.IsNewTransaction, "Is new transaction");
            throw exception;
        }
    }

    #endregion

    #region Supporting class for ParticipatingTxWithRollbackOnly test

    internal class ParticipatingTxWithRollbackOnlyTxCallback : ITransactionCallback
    {
        private TransactionTemplate innerTxTemplate;
        private IDbProvider dbProvider;


        public ParticipatingTxWithRollbackOnlyTxCallback(TransactionTemplate transactionTemplate, IDbProvider dbProvider)
        {
            innerTxTemplate = transactionTemplate;
            this.dbProvider = dbProvider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(!status.IsNewTransaction, "Is existing transaction");
            Assert.IsFalse(status.RollbackOnly, "Is not rollback-only");
            innerTxTemplate.Execute(new ParticipatingTxWithRollbackOnlyTxCallback2(dbProvider));
            Assert.IsTrue(!status.IsNewTransaction, "Is existing transaction");
            Assert.IsTrue(status.RollbackOnly, "Is rollback-only");
            return null;
        }
    }

    internal class ParticipatingTxWithRollbackOnlyTxCallback2 : ITransactionCallback
    {
        private IDbProvider dbProvider;

        public ParticipatingTxWithRollbackOnlyTxCallback2(IDbProvider dbProvider)
        {
            this.dbProvider = dbProvider;
        }

        public object DoInTransaction(ITransactionStatus status)
        {
            Assert.IsTrue(TransactionSynchronizationManager.HasResource(dbProvider), "Has thread connection");
            Assert.IsTrue(TransactionSynchronizationManager.SynchronizationActive, "Synchronization active");
            Assert.IsTrue(!status.IsNewTransaction, "Is existing transaction");
            status.SetRollbackOnly();
            return null;
        }
    }

    #endregion



    #region Helper class

    internal class TestTransactionSynchronization : ITransactionSynchronization
    {
        private IDbProvider provider;
        private TransactionSynchronizationStatus status;

        public bool beforeCommitCalled;
        public bool beforeCompletionCalled;
        public bool afterCommitCalled;
        public bool afterCompletionCalled;

        public TestTransactionSynchronization(IDbProvider provider,
                                              TransactionSynchronizationStatus synchronizationStatus)
        {
            this.provider = provider;
            status = synchronizationStatus;
        }


        public void Suspend()
        {
        }

        public void Resume()
        {
        }

        public void BeforeCommit(bool readOnly)
        {
            if (status == TransactionSynchronizationStatus.Committed)
            {
                Assert.Fail("Should never be called");
            }
            Assert.IsFalse(beforeCommitCalled);
            beforeCommitCalled = true;
        }

        public void AfterCommit()
        {
            if (status != TransactionSynchronizationStatus.Committed)
            {
                Assert.Fail("Should never be called");
            }
            Assert.IsFalse(afterCommitCalled);
            afterCommitCalled = true;
        }

        public void BeforeCompletion()
        {
            Assert.IsFalse(beforeCompletionCalled);
            beforeCompletionCalled = true;
        }

        public void AfterCompletion(TransactionSynchronizationStatus syncStatus)
        {
            Assert.IsFalse(afterCompletionCalled);
            afterCompletionCalled = true;
            Assert.IsTrue(syncStatus == status);
            Assert.IsTrue(TransactionSynchronizationManager.HasResource(provider));
        }
    }

    #endregion
}