package com.opower.updater.operation;
import com.opower.updater.LayoutUpdate;
import com.opower.updater.admin.Update;
import com.opower.updater.admin.loader.ResourceUpdateLoader;
import org.junit.Before;
import org.junit.Test;
import org.kiji.schema.shell.DDLException;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
import java.io.IOException;
import java.util.SortedSet;
import java.util.TreeSet;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
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.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* Tests for {@link com.opower.updater.operation.TableUpdater}.
*
* @author felix.trepanier
*/
public class TestTableUpdater extends BaseTableOperatorTest {
private SortedSet<Update> allUpdates;
private TableUpdater updater;
private TableDDLGenerator mockTableDDLGenerator;
@Before
@Override
public void setup() throws IOException {
super.setup();
allUpdates = ResourceUpdateLoader.DEFAULT.loadUpdates(TABLE_NAME);
mockTableDDLGenerator = mock(TableDDLGenerator.class);
updater = new TableUpdater(mockDDLRunner, mockUpdateTable, mockMetaTable, mockTableDDLGenerator);
}
@Test
public void testUpdateFailsIfTheTableDoesNotExistInKijiInstance() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(false);
try {
updater.updateTable(TABLE_NAME, allUpdates, false);
fail();
}
catch (TableDoesNotExistException e) {
// ok
}
verifyNoDDLNoLayoutUpdate();
}
@Test
public void testUpdateFailsIfTheTableDoesNotExistInLayoutUpdateIfBootstrapIsFalse() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(false);
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(null);
try {
updater.updateTable(TABLE_NAME, allUpdates, false);
fail();
}
catch (TableDoesNotExistException e) {
// ok
}
verifyNoDDLNoLayoutUpdate();
}
@Test
public void testAllUpdatesApplied() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(0);
updater.updateTable(TABLE_NAME, allUpdates, false);
verify(mockDDLRunner).execute(getFirstUpdate().getDDL());
verify(mockDDLRunner).execute(getSecondUpdate().getDDL());
}
@Test
public void testUpdateResult() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(0);
TableUpdater.UpdateResult result = updater.updateTable(TABLE_NAME, allUpdates, false);
assertEquals(TABLE_NAME, result.getTableName());
assertEquals(new Integer(2), result.getNumberOfUpdatesApplied());
assertFalse(result.wasBootstrapped());
}
@Test
public void testUpdatesStartAfterLastKnownId() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(1);
updater.updateTable(TABLE_NAME, allUpdates, false);
verify(mockDDLRunner).execute(allUpdates.last().getDDL());
verifyNoMoreInteractions(mockDDLRunner);
}
@Test
public void testLayoutUpdateTableIsUpdated() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(0);
updater.updateTable(TABLE_NAME, allUpdates, false);
LayoutUpdate firstLayoutUpdate = LayoutUpdate.newBuilder()
.setUpdateId(1)
.setChecksum("")
.setValidationFunctionVersion(0)
.setAppliedDDL(getFirstUpdate().getDDL()).build();
LayoutUpdate secondLayoutUpdate = LayoutUpdate.newBuilder()
.setUpdateId(2)
.setChecksum("")
.setValidationFunctionVersion(0)
.setAppliedDDL(getSecondUpdate().getDDL()).build();
verify(mockUpdateTable).insertLayoutUpdate(TABLE_NAME, firstLayoutUpdate);
verify(mockUpdateTable).insertLayoutUpdate(TABLE_NAME, secondLayoutUpdate);
}
@Test
public void testMissingUpdateIdFails() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
allUpdates.add(new Update(88, "DUMMY"));
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(1);
try {
updater.updateTable(TABLE_NAME, allUpdates, false);
fail();
}
catch (MissingUpdateException e) {
// ok
}
ArgumentCaptor<LayoutUpdate> updateCaptor = ArgumentCaptor.forClass(LayoutUpdate.class);
verify(mockUpdateTable, times(1)).insertLayoutUpdate(eq(TABLE_NAME), updateCaptor.capture());
assertEquals(new Integer(2), updateCaptor.getValue().getUpdateId());
}
@Test
public void testMissingUpdateOneFails() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
SortedSet<Update> updates = new TreeSet<Update>(Update.UPDATE_COMPARATOR);
updates.add(new Update(88, "DUMMY"));
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(0);
try {
updater.updateTable(TABLE_NAME, updates, false);
fail();
}
catch (MissingUpdateException e) {
// ok
}
verifyNoDDLNoLayoutUpdate();
}
@Test
public void testUpdateProcessAbortOnDDLExecutionFailure() throws IOException {
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(1);
doThrow(new DDLException("Bad ORIGINAL_DDL Statement")).when(mockDDLRunner).execute(anyString());
Update badUpdate = new Update(2, "ALTER TABLE test ADD COLUMN wrong:wrong WITH SCHEMA ID 0;");
TreeSet<Update> updates = new TreeSet<Update>(Update.UPDATE_COMPARATOR);
updates.add(badUpdate);
try {
updater.updateTable(TABLE_NAME, updates, false);
fail();
}
catch (UpdateException e) {
assertEquals(2, e.getFailedUpdate().getId());
}
verify(mockUpdateTable, never()).insertLayoutUpdate(anyString(), (LayoutUpdate) anyObject());
}
@Test
public void supportTheUpdateOfAnExistingTableCreatedPriorToTheUseOfTheUpdaterTool() throws IOException {
String createDDL = "CREATE DDL STATEMENT;";
when(mockMetaTable.tableExists(TABLE_NAME)).thenReturn(true);
when(mockUpdateTable.getLastUpdateIdForTable(TABLE_NAME)).thenReturn(null, 0);
when(mockTableDDLGenerator.generateTableLayoutDDL(TABLE_NAME)).thenReturn(createDDL);
TableUpdater.UpdateResult result = updater.updateTable(TABLE_NAME, allUpdates, true);
assertTrue(result.wasBootstrapped());
ArgumentCaptor<LayoutUpdate> layoutUpdateCaptor = ArgumentCaptor.forClass(LayoutUpdate.class);
verify(mockUpdateTable, times(3)).insertLayoutUpdate(Matchers.eq(TABLE_NAME), layoutUpdateCaptor.capture());
LayoutUpdate initialLayoutUpdate = layoutUpdateCaptor.getAllValues().get(0);
assertEquals(new Integer(0), initialLayoutUpdate.getUpdateId());
assertEquals(createDDL, initialLayoutUpdate.getAppliedDDL());
LayoutUpdate firstLayoutUpdate = LayoutUpdate.newBuilder()
.setUpdateId(getFirstUpdate().getId())
.setChecksum("")
.setValidationFunctionVersion(0)
.setAppliedDDL(getFirstUpdate().getDDL()).build();
LayoutUpdate secondLayoutUpdate = LayoutUpdate.newBuilder()
.setUpdateId(getSecondUpdate().getId())
.setChecksum("")
.setValidationFunctionVersion(0)
.setAppliedDDL(getSecondUpdate().getDDL()).build();
assertEquals(firstLayoutUpdate, layoutUpdateCaptor.getAllValues().get(1));
assertEquals(secondLayoutUpdate, layoutUpdateCaptor.getAllValues().get(2));
}
private Update getFirstUpdate() {
return allUpdates.tailSet(Update.fromUpdate(1)).first();
}
private Update getSecondUpdate() {
return allUpdates.tailSet(Update.fromUpdate(2)).first();
}
}