package com.adamlewis.guice.persist.jooq;

import java.lang.reflect.Method;

import com.adamlewis.guice.persist.jooq.utils.MockConnection;
import com.adamlewis.guice.persist.jooq.utils.Providers;
import com.google.inject.persist.Transactional;
import com.google.inject.persist.UnitOfWork;
import org.aopalliance.intercept.MethodInvocation;
import org.jooq.impl.DefaultConnectionProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class JdbcLocalTxnInterceptorTest {
  @Mock
  private JooqPersistService jooqPersistService;
  @Mock
  private UnitOfWork unitOfWork;
  @Mock
  private MockConnection connection;
  @Mock
  private MethodInvocation methodInvocation;

  private JdbcLocalTxnInterceptor interceptor;

  @Transactional
  public void transaction() {
  }

  @Before
  public void setUp() throws Exception {
    interceptor = new JdbcLocalTxnInterceptor(Providers.of(jooqPersistService), Providers.of(unitOfWork));

    when(connection.getAutoCommit()).thenCallRealMethod();
    doCallRealMethod().when(connection).setAutoCommit(anyBoolean());
    connection.setAutoCommit(true);

    DefaultConnectionProvider connectionProvider = new DefaultConnectionProvider(connection);
    when(jooqPersistService.getConnectionWrapper()).thenReturn(connectionProvider);
    when(jooqPersistService.isWorking()).thenReturn(false);

    // Method is final. Mockito doesn't support mocking final classes. Using reflection
    Method defaultTransaction = JdbcLocalTxnInterceptorTest.class.getMethod("transaction");
    when(methodInvocation.getMethod()).thenReturn(defaultTransaction);
  }

  @Test
  public void unitOfWorkEnds() throws Throwable {
    interceptor.invoke(methodInvocation);

    verify(unitOfWork).begin();
    verify(connection).commit();
    verify(unitOfWork).end();
  }

  @Test
  public void unitOfWorkEndsOnException() throws Throwable {
    when(methodInvocation.proceed()).thenThrow(Exception.class);

    try {
      interceptor.invoke(methodInvocation);
      fail("exception expected");
    } catch (Exception ignored) {
    }

    verify(unitOfWork).begin();
    verify(connection).commit();
    verify(unitOfWork).end();
  }

  @Test
  public void unitOfWorkEndsOnRollbackException() throws Throwable {
    when(methodInvocation.proceed()).thenThrow(RuntimeException.class);

    try {
      interceptor.invoke(methodInvocation);
      fail("exception expected");
    } catch (RuntimeException ignored) {
    }

    verify(unitOfWork).begin();
    verify(connection).rollback();
    verify(unitOfWork).end();
  }
}