/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 de.unioninvestment.eai.portal.portlet.crud.domain.model;

import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import java.sql.Clob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.derby.iapi.types.SQLTimestamp;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import com.vaadin.data.Item;
import com.vaadin.data.util.sqlcontainer.ColumnProperty;
import com.vaadin.data.util.sqlcontainer.RowId;
import com.vaadin.data.util.sqlcontainer.RowItem;
import com.vaadin.data.util.sqlcontainer.SQLContainer;
import com.vaadin.data.util.sqlcontainer.TemporaryRowId;

import de.unioninvestment.eai.portal.portlet.crud.domain.container.EditorSupport;
import de.unioninvestment.eai.portal.portlet.crud.domain.container.TimeoutableQueryDelegate;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.BeforeCommitEvent;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.CommitEvent;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.CommitEventHandler;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.CreateEvent;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.CreateEventHandler;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.DeleteEvent;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.DeleteEventHandler;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.InsertEvent;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.InsertEventHandler;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.UpdateEvent;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.UpdateEventHandler;
import de.unioninvestment.eai.portal.portlet.crud.domain.exception.ContainerException;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.DataContainer.EachRowCallback;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.DataContainer.TransactionCallback;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.filter.Contains;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.filter.Filter;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.filter.RegExpFilter;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.filter.SQLFilter;
import de.unioninvestment.eai.portal.support.vaadin.filter.AdvancedStringFilter;
import de.unioninvestment.eai.portal.support.vaadin.mvp.EventBus;
import de.unioninvestment.eai.portal.support.vaadin.mvp.EventRouter;
import de.unioninvestment.eai.portal.support.vaadin.table.DisplaySupport;

public abstract class AbstractDatabaseContainerTest<T extends AbstractDatabaseContainer, V extends SQLContainerEventWrapper>
		extends AbstractDataContainerTest<T, V> {

	private TestContainer testContainer;

	@Mock
	private RowId rowId1Mock;

	@Mock
	private RowId rowId2Mock;

	@Mock
	private ContainerRowId containerRowId1Mock;

	@Mock
	private EachRowCallback eachRowCallbackMock;

	@Captor
	private ArgumentCaptor<ContainerRow> rowCaptor;

	@Mock
	private EventBus eventBus;

	@Before
	public void databaseSetUp() {
		testContainer = new TestContainer(eventBus, editors,
				vaadinContainerMock, displayPatternMock);
	}

	private class TestContainer extends AbstractDatabaseContainer {

		private static final long serialVersionUID = 42L;

		private final String[] primaryKeys;

		public TestContainer(EventBus eventBus, List<EditorSupport> editors,
				SQLContainerEventWrapper container,
				Map<String, String> displayPattern) {
			super(eventBus, displayPattern, null, null);
			primaryKeys = null;
			super.editors = editors;
			super.container = container;
		}

		public TestContainer(EventBus eventBus, String[] primaryKeys,
				List<EditorSupport> editors,
				Map<String, String> displayPattern,
				List<ContainerOrder> defaultOrder) {
			super(eventBus, displayPattern, defaultOrder, null);
			this.primaryKeys = primaryKeys;
			super.editors = editors;
		}

		@Override
		public String getDatasource() {
			return "eai";
		}

		@Override
		public boolean isInsertable() {
			return false;
		}

		@Override
		public boolean isUpdateable() {
			return false;
		}

		@Override
		public boolean isDeleteable() {
			return false;
		}

		@Override
		public List<String> getColumns() {
			return null;
		}
		
		@Override
		public List<String> getPrimaryKeyColumns() {
			return asList(primaryKeys);
		}

		@Override
		protected SQLContainerEventWrapper createVaadinContainer() {
			return vaadinContainerMock;
		}

		@Override
		public EventRouter<UpdateEventHandler, UpdateEvent> getOnUpdateEventRouter() {
			return super.getOnUpdateEventRouter();
		}

		@Override
		protected EventRouter<InsertEventHandler, InsertEvent> getOnInsertEventRouter() {
			return super.getOnInsertEventRouter();
		}

		@Override
		protected EventRouter<DeleteEventHandler, DeleteEvent> getOnDeleteEventRouter() {
			return super.getOnDeleteEventRouter();
		}

		@Override
		protected EventRouter<CreateEventHandler, CreateEvent> getOnCreateEventRouter() {
			return super.getOnCreateEventRouter();
		}

		@Override
		public EventRouter<CommitEventHandler, CommitEvent> getOnCommitEventRouter() {
			return super.getOnCommitEventRouter();
		}

		@Override
		public DisplaySupport findDisplayer(String columnName) {
			return null;
		}

		@Override
		public ContainerClob getCLob(ContainerRowId rowId, String columnName) {
			return null;
		}

		@Override
		public boolean isCLobModified(ContainerRowId containerRowId,
				String columnName) {
			return false;
		}

		@Override
		public ContainerBlob getBLob(ContainerRowId rowId, String columnName) {
			return null;
		}

		@Override
		public boolean isBLobModified(ContainerRowId containerRowId,
				String columnName) {
			return false;
		}

		@Override
		public boolean isBLobEmpty(ContainerRowId rowId, String columnName) {
			// TODO Auto-generated method stub
			return false;
		}
	}

	@Test
	public void shouldReturnContainerItemIds() {
		testContainer.getRowIds();
		verify(vaadinContainerMock, times(1)).getItemIds();
	}

	@Test
	public void shouldFireBeforeCommitEventOnCommit() {
		testContainer.addBeforeCommitEventHandler(beforeCommitEventHandlerMock);
		testContainer.commit();
		verify(beforeCommitEventHandlerMock).beforeCommit(
				new BeforeCommitEvent(testContainer));
	}

	@Test
	public void shouldFireCommitEventOnVaadinCommit() {
		testContainer.addCommitEventHandler(commitEventHandlerMock);
		when(vaadinContainerMock.isModified()).thenReturn(true);
		testContainer.commit();
		verify(commitEventHandlerMock).onCommit(new CommitEvent(testContainer));
	}

	@Test
	public void shouldNotFireCommitEventOnUnmodifiedContainer() {
		testContainer.addCommitEventHandler(commitEventHandlerMock);
		when(vaadinContainerMock.isModified()).thenReturn(false);
		testContainer.commit();
		verify(commitEventHandlerMock, never()).onCommit(
				new CommitEvent(testContainer));
	}

	@Test
	public void shouldCallSortOnContainerWithDefaultOrder() {
		ContainerOrder order1 = new ContainerOrder("ID", true);
		ContainerOrder order2 = new ContainerOrder("NAME", false);
		TestContainer container = new TestContainer(eventBus, new String[] {
				"ID", "INDEX", "NAME" }, editors, displayPatternMock, asList(
				order1, order2));

		container.getVaadinContainer();

		verify(vaadinContainerMock).sort(new Object[] { "ID", "NAME" },
				new boolean[] { true, false });
	}

	@Test
	public void shouldNotCallSortOnContainerWithoutDefaultOrder() {

		TestContainer container = new TestContainer(eventBus, new String[] {
				"ID", "INDEX", "NAME" }, editors, displayPatternMock, null);

		container.getVaadinContainer();

		verify(vaadinContainerMock, never()).sort(any(Object[].class),
				any(boolean[].class));
	}

	@Test
	public void shouldNotCallVaadinContainerCommitIfUnmodified()
			throws UnsupportedOperationException, SQLException {
		when(vaadinContainerMock.isModified()).thenReturn(false);
		testContainer.commit();
		verify(vaadinContainerMock, never()).commit();
	}

	@Test
	public void shouldCallVaadinContainerCommitIfModified()
			throws UnsupportedOperationException, SQLException {
		when(vaadinContainerMock.isModified()).thenReturn(true);
		testContainer.commit();
		verify(vaadinContainerMock).commit();
	}

	@Test
	public void shouldConvertRowItemToDatabaseContainerRow() {
		TestContainer container = new TestContainer(eventBus, new String[] {
				"ID", "INDEX", "NAME" }, editors, displayPatternMock, null);
		RowId rowId = new RowId(new Object[] { 1, 2, "MY_NAME" });
		ColumnProperty idProperty = new ColumnProperty("ID", false, false,
				true, false, "ID", String.class);
		ColumnProperty indexProperty = new ColumnProperty("INDEX", false,
				false, true, false, "ID", String.class);
		ColumnProperty nameProperty = new ColumnProperty("NAME", false, false,
				true, false, "ID", String.class);
		Item item = new RowItem(vaadinContainerMock, rowId, Arrays.asList(
				idProperty, indexProperty, nameProperty));

		ContainerRow row = container.convertItemToRow(item, true, false);
		assertThat(row.getInternalRow(), is(item));
	}

	@Test
	public void shouldConvertInternalRowIds() {
		RowId rowId = new RowId(new Object[] { 1, 2, "MY_NAME" });
		TestContainer container = new TestContainer(eventBus, new String[] {
				"ID", "INDEX", "NAME" }, editors, displayPatternMock, null);

		ContainerRowId id = container.convertInternalRowId(rowId);

		assertThat(id.asMap().size(), is(3));
		assertThat(id.asMap().get("ID"), is((Object) 1));
		assertThat(id.asMap().get("INDEX"), is((Object) 2));
		assertThat(id.asMap().get("NAME"), is((Object) "MY_NAME"));
	}

	@Test
	public void shouldAllowTransactionalCallbackOperations()
			throws UnsupportedOperationException, SQLException {
		when(vaadinContainerMock.isModified()).thenReturn(true);
		testContainer
				.withTransaction(new DataContainer.TransactionCallback<Object>() {
					@Override
					public Object doInTransaction() {
						return null;
					}
				});
		verify(vaadinContainerMock, times(2)).commit();
	}

	@Test(expected = ContainerException.class)
	public void shouldNotThrowExceptionOnRollback() throws Exception {
		doThrow(new SQLException()).when(vaadinContainerMock).rollback();

		testContainer.rollback();
	}

	@Test
	public void shouldRollbackOnExceptionDuringTransaction()
			throws SQLException {
		when(vaadinContainerMock.isModified()).thenReturn(true);
		try {
			testContainer
					.withTransaction(new DataContainer.TransactionCallback<Object>() {

						@Override
						public Object doInTransaction() {
							throw new IllegalArgumentException("Bla");
						}

					});
			fail();

		} catch (IllegalArgumentException e) {
			// ok
		}
		verify(vaadinContainerMock).commit(); // user transaction
		verify(vaadinContainerMock).rollback();
	}

	@Test
	public void shouldRollbackOnExceptionDuringCommit() throws SQLException {
		when(vaadinContainerMock.isModified()).thenReturn(true);
		try {
			testContainer
					.withTransaction(new DataContainer.TransactionCallback<Object>() {
						@Override
						public Object doInTransaction() {
							try {
								doThrow(new SQLException("Commit failed"))
										.when(vaadinContainerMock).commit();
							} catch (SQLException e) {
								fail();
							}
							return null;
						}

					});
			fail();

		} catch (ContainerException e) {
			// ok
		}
		verify(vaadinContainerMock, times(2)).commit(); // user transaction
		verify(vaadinContainerMock).rollback();
	}

	@Test
	public void shouldJoinExistingTransaction() throws SQLException {
		when(vaadinContainerMock.isModified()).thenReturn(true);
		testContainer
				.withTransaction(new DataContainer.TransactionCallback<Object>() {

					@Override
					public Object doInTransaction() {

						testContainer
								.withTransaction(new TransactionCallback<Object>() {

									@Override
									public Object doInTransaction() {
										try {
											verify(vaadinContainerMock)
													.commit();

										} catch (SQLException e) {
											fail();
										}
										return null;
									}
								});
						return null;
					}
				});
		verify(vaadinContainerMock, times(2)).commit();
	}

	@Test
	public void shouldCommitPossibleUserTransactionBeforeStartingATransaction()
			throws SQLException {
		when(vaadinContainerMock.isModified()).thenReturn(true);
		testContainer
				.withTransaction(new DataContainer.TransactionCallback<Object>() {

					@Override
					public Object doInTransaction() {
						try {
							verify(vaadinContainerMock).commit();

						} catch (UnsupportedOperationException e) {
							fail();
						} catch (SQLException e) {
							fail();
						}
						return null;
					}
				});
	}

	@Test
	public void shouldAddSqlFilter() {
		shouldAddFilterType(
				new SQLFilter("NAME", "bla", Arrays.asList((Object) "Test")),
				de.unioninvestment.eai.portal.support.vaadin.filter.SQLFilter.class);
	}

	@Test
	public void shouldAddRegExpFilter() {
		shouldAddFilterType(
				new RegExpFilter("NAME", "bla", "i"),
				de.unioninvestment.eai.portal.support.vaadin.filter.DatabaseRegExpFilter.class);
	}

	@Test
	public void shouldAllowRequiringExistingTransaction() throws SQLException {
		final AtomicBoolean called = new AtomicBoolean();
		when(vaadinContainerMock.isModified()).thenReturn(true);
		testContainer
				.withTransaction(new DataContainer.TransactionCallback<Object>() {

					@Override
					public Object doInTransaction() {
						testContainer
								.withExistingTransaction(new TransactionCallback<Object>() {

									@Override
									public Object doInTransaction() {
										called.set(true);
										return null;
									}

								});
						return null;
					}
				});
		assertThat(called.get(), is(true));
		verify(vaadinContainerMock, times(2)).commit();
	}

	@Test(expected = IllegalStateException.class)
	public void shouldHandleMissingExistingTransaction() throws SQLException {
		final TestContainer container = new TestContainer(eventBus, editors,
				vaadinContainerMock, displayPatternMock);
		container.withExistingTransaction(new TransactionCallback<Object>() {
			@Override
			public Object doInTransaction() {
				return null;
			}
		});
	}

	@Test
	public void shouldRemoveGivenRows() {
		HashSet<ContainerRowId> ids = new HashSet<ContainerRowId>();
		when(rowId1Mock.getId()).thenReturn(new Object[] { 1, 2 });
		when(rowId2Mock.getId()).thenReturn(new Object[] { 1, 3 });
		ids.add(new DatabaseContainerRowId(rowId1Mock, asList("A", "B")));
		ids.add(new DatabaseContainerRowId(rowId2Mock, asList("A", "B")));

		testContainer.removeRows(ids);

		verify(vaadinContainerMock).removeItem(rowId1Mock);
		verify(vaadinContainerMock).removeItem(rowId2Mock);
	}

	@SuppressWarnings("unchecked")
	@Test
	public void shouldAddNewRow() {

		container = (T) new TestContainer(eventBus, new String[] { "ID" },
				editors, displayPatternMock, null);

		TemporaryRowId rowId = new TemporaryRowId(new Object[] { "ID" });
		Item row1 = createRow(rowId);

		when(vaadinContainerMock.addItem()).thenReturn(rowId);
		when(vaadinContainerMock.getItem(rowId)).thenReturn(row1);

		ContainerRow containerRow = container
				.withTransaction(new TransactionCallback<ContainerRow>() {
					@Override
					public ContainerRow doInTransaction() {
						return container.addRow();
					}
				});

		assertThat(containerRow.getInternalRow(), is(row1));
	}

	protected RowItem createRow(RowId rowId) {
		return new RowItem(vaadinContainerMock, rowId,
				new HashSet<ColumnProperty>());
	}

	@Test
	public void shouldReturnCachedRowById() {
		TestContainer container = new TestContainer(eventBus, new String[] {
				"ID", "INDEX", "NAME" }, editors, displayPatternMock, null);

		when(rowId1Mock.getId()).thenReturn(new Object[] { 1 });
		Item row1 = createRow(rowId1Mock);
		DatabaseContainerRowId containerRowId1 = new DatabaseContainerRowId(
				rowId1Mock, asList("ID"));

		when(vaadinContainerMock.getItemUnfiltered(rowId1Mock))
				.thenReturn(row1);

		ContainerRow cachedRow = container.getCachedRow(containerRowId1, true,
				false);
		assertThat(cachedRow.getInternalRow(), is(row1));
	}

	@Test
	public void shouldReturnRowById() {
		TestContainer container = new TestContainer(eventBus, new String[] {
				"ID", "INDEX", "NAME" }, editors, displayPatternMock, null);

		when(rowId1Mock.getId()).thenReturn(new Object[] { 1 });
		Item row1 = createRow(rowId1Mock);
		DatabaseContainerRowId containerRowId1 = new DatabaseContainerRowId(
				rowId1Mock, asList("ID"));

		when(vaadinContainerMock.getItem(rowId1Mock)).thenReturn(row1);

		ContainerRow cachedRow = container.getRow(containerRowId1, true, false);
		assertThat(cachedRow.getInternalRow(), is(row1));
	}

	@Test
	public void shouldReturnNullWhenAskingForNonExistentRows() {
		when(rowId1Mock.getId()).thenReturn(new Object[] { 1 });
		DatabaseContainerRowId containerRowId1 = new DatabaseContainerRowId(
				rowId1Mock, asList("ID"));

		when(vaadinContainerMock.getItem(rowId1Mock)).thenReturn(null);

		assertThat(testContainer.getRow(containerRowId1, true, false),
				nullValue());
	}

	@Test
	public void shouldReturnNullWhenAskingForUncachedRows() {
		when(rowId1Mock.getId()).thenReturn(new Object[] { 1 });
		DatabaseContainerRowId containerRowId1 = new DatabaseContainerRowId(
				rowId1Mock, asList("ID"));

		when(vaadinContainerMock.getItemUnfiltered(rowId1Mock))
				.thenReturn(null);

		assertThat(testContainer.getCachedRow(containerRowId1, true, false),
				nullValue());
	}

	@Test
	public void shouldCallRefreshOnDBContainer() {
		testContainer.refresh();
		verify(vaadinContainerMock).refresh();
	}

	@Test
	public void shouldFindEditor() {
		returnVaadinColumnType("Test", SQLTimestamp.class);

		when(editorSupportMock.supportsEditing(SQLTimestamp.class)).thenReturn(
				true);

		EditorSupport editor = testContainer.findEditor("Test");

		assertThat(editor, is(editorSupportMock));
	}

	@Test
	public void shouldNotFindEditor() {
		returnVaadinColumnType("Test", SQLTimestamp.class);

		when(editorSupportMock.supportsEditing(SQLTimestamp.class)).thenReturn(
				false);

		EditorSupport editor = testContainer.findEditor("Test");

		assertThat(editor, is(nullValue()));
	}

	@Test
	public void shouldCheckClobColumnType() {
		returnVaadinColumnType("TestCLob", Clob.class);
		assertThat(testContainer.isCLob("TestCLob"), is(true));

	}

	private void returnVaadinColumnType(String columnName, final Class<?> clazz) {
		when(vaadinContainerMock.getType(any())).thenAnswer(
				new Answer<Class<?>>() {
					@Override
					public Class<?> answer(InvocationOnMock invocation)
							throws Throwable {
						return clazz;
					}
				});
	}

	@Test
	public void shouldReturnFalseForClobIfBackendGivesNullType() {
		returnVaadinColumnType("TestCLob", null);
		assertThat(testContainer.isCLob("TestCLob"), is(false));
	}

	@Test
	public void shouldReturnFalseForBlobIfBackendGivesNullType() {
		returnVaadinColumnType("TestBLob", null);
		assertThat(testContainer.isBLob("TestBLob"), is(false));

	}

	@Test
	public void getTypeShouldReturnNullOnMissingTypeInformation() {
		assertNull(testContainer.getType("UNKNOWN"));
	}

	@Test
	public void shouldAddContainsFilter() {
		shouldAddFilterType(new Contains("NAME", "abcdef", false),
				AdvancedStringFilter.class);
	}

	@Test
	public void shouldSetTimeoutAtQueryDelegateAndRestoreOldTimeout() {
		this.testContainer.queryDelegate = Mockito
				.mock(TimeoutableQueryDelegate.class);

		when(this.testContainer.queryDelegate.getQueryTimeout()).thenReturn(42);

		this.testContainer.replaceFilters(new ArrayList<Filter>(), false, false, 9);

		verify(this.testContainer.queryDelegate).setQueryTimeout(9);

		assertThat(this.testContainer.queryDelegate.getQueryTimeout(), is(42));
	}

	@Test
	public void shouldAllowTraversalOfEachContainerRow() {
		container.queryDelegate = mock(TimeoutableQueryDelegate.class);
		container.setVaadinContainer(vaadinContainerMock);

		when(vaadinContainerMock.firstItemId()).thenReturn(rowId1Mock);
		ColumnProperty property = new ColumnProperty("id", false, false, true,
				false, 1, Integer.class);
		RowItem rowItem1 = new RowItem((SQLContainer) vaadinContainerMock,
				rowId1Mock, asList(property));
		when(vaadinContainerMock.getItem(rowId1Mock)).thenReturn(rowItem1);

		when(vaadinContainerMock.nextItemId(rowId1Mock)).thenReturn(rowId2Mock);
		ColumnProperty property2 = new ColumnProperty("id", false, false, true,
				false, 1, Integer.class);
		RowItem rowItem2 = new RowItem((SQLContainer) vaadinContainerMock,
				rowId2Mock, asList(property2));
		when(vaadinContainerMock.getItem(rowId2Mock)).thenReturn(rowItem2);

		when(vaadinContainerMock.nextItemId(rowId2Mock)).thenReturn(null);

		container.eachRow(eachRowCallbackMock);

		verify(vaadinContainerMock).firstItemId();
		verify(vaadinContainerMock).nextItemId(rowId1Mock);
		verify(vaadinContainerMock).nextItemId(rowId2Mock);

		verify(eachRowCallbackMock, times(2)).doWithRow(rowCaptor.capture());
		List<ContainerRow> allRows = rowCaptor.getAllValues();
		assertThat(allRows.get(0).getInternalRow(),
				sameInstance((Item) rowItem1));
		assertThat(allRows.get(1).getInternalRow(),
				sameInstance((Item) rowItem2));

	}

	@Test
	public void shouldDoNothingOnTraversalOfEmptyList() {
		container.setVaadinContainer(vaadinContainerMock);

		when(vaadinContainerMock.firstItemId()).thenReturn(null);

		container.eachRow(eachRowCallbackMock);

		verify(vaadinContainerMock).firstItemId();
		verifyZeroInteractions(eachRowCallbackMock);
	}

	@Test
	public void shouldReturnNullAsNextRowIdOnLastRowId() {
		container.setVaadinContainer(vaadinContainerMock);
		when(containerRowId1Mock.getInternalId()).thenReturn(rowId1Mock);
		when(vaadinContainerMock.nextItemId(rowId1Mock)).thenReturn(null);

		ContainerRowId nextRowId = container.nextRowId(containerRowId1Mock);

		assertThat(nextRowId, nullValue());
	}

	@Test
	public void shouldReturnNextRowId() {
		container.setVaadinContainer(vaadinContainerMock);
		when(containerRowId1Mock.getInternalId()).thenReturn(rowId1Mock);
		when(vaadinContainerMock.nextItemId(rowId1Mock)).thenReturn(rowId2Mock);

		ContainerRowId nextRowId = container.nextRowId(containerRowId1Mock);

		assertThat(nextRowId.getInternalId(), is((Object) rowId2Mock));
	}

	@Test
	public void shouldReturnNullAsPreviousRowIdOnLastRowId() {
		container.setVaadinContainer(vaadinContainerMock);
		when(containerRowId1Mock.getInternalId()).thenReturn(rowId1Mock);
		when(vaadinContainerMock.prevItemId(rowId1Mock)).thenReturn(null);

		ContainerRowId previousRowId = container
				.previousRowId(containerRowId1Mock);

		assertThat(previousRowId, nullValue());
	}

	@Test
	public void shouldReturnPreviousRowId() {
		container.setVaadinContainer(vaadinContainerMock);
		when(containerRowId1Mock.getInternalId()).thenReturn(rowId1Mock);
		when(vaadinContainerMock.prevItemId(rowId1Mock)).thenReturn(rowId2Mock);

		ContainerRowId previousRowId = container
				.previousRowId(containerRowId1Mock);

		assertThat(previousRowId.getInternalId(), is((Object) rowId2Mock));
	}

}
