blob: e506e260ff1026a840f1bdda39527b398db4f8e9 [file] [log] [blame]
// Copyright (C) 2014 Ericsson
//
// 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.
package com.ericsson.gerrit.plugins.eventslog.sql;
import static com.ericsson.gerrit.plugins.eventslog.sql.SQLTable.TABLE_NAME;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.events.ProjectEvent;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gson.Gson;
import com.google.inject.Provider;
import com.ericsson.gerrit.plugins.eventslog.EventsLogConfig;
import com.ericsson.gerrit.plugins.eventslog.MalformedQueryException;
import com.ericsson.gerrit.plugins.eventslog.ServiceUnavailableException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ConnectException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@RunWith(MockitoJUnitRunner.class)
public class SQLStoreTest {
private static final Logger log = LoggerFactory.getLogger(SQLStoreTest.class);
private static final String TEST_URL = "jdbc:h2:mem:" + TABLE_NAME;
private static final String TEST_LOCAL_URL = "jdbc:h2:mem:test";
private static final String TEST_DRIVER = "org.h2.Driver";
private static final String TEST_OPTIONS =
"DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false";
private static final String TERM_CONN_MSG = "terminating connection";
private static final String MSG = "message";
private static final String GENERIC_QUERY = "SELECT * FROM " + TABLE_NAME;
private static final boolean PROJECT_VISIBLE_TO_USER = true;
private static final boolean PROJECT_NOT_VISIBLE_TO_USER = false;
@Mock
private ProjectControl.GenericFactory pcFactoryMock;
@Mock
private Provider<CurrentUser> userProviderMock;
@Mock
private EventsLogConfig cfgMock;
private SQLClient eventsDb;
private SQLClient localEventsDb;
private SQLStore store;
private ScheduledThreadPoolExecutor poolMock;
private Statement stat;
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
@Before
public void setUp() throws SQLException {
Connection conn =
DriverManager.getConnection(TEST_URL + ";" + TEST_OPTIONS);
stat = conn.createStatement();
poolMock = new PoolMock();
when(cfgMock.getMaxAge()).thenReturn(5);
when(cfgMock.getLocalStorePath()).thenReturn(testFolder.getRoot().toPath());
}
public void tearDown() throws Exception {
stat.execute("DROP TABLE " + TABLE_NAME);
store.stop();
}
private void setUpClient() {
eventsDb = new SQLClient(TEST_DRIVER, TEST_URL, TEST_OPTIONS);
localEventsDb = new SQLClient(TEST_DRIVER, TEST_LOCAL_URL, TEST_OPTIONS);
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
}
private void setUpClientMock() throws SQLException {
eventsDb = mock(SQLClient.class);
localEventsDb = mock(SQLClient.class);
when(localEventsDb.dbExists()).thenReturn(true);
}
@Test
public void storeThenQueryVisible() throws Exception {
MockEvent mockEvent = setUpMocks(PROJECT_VISIBLE_TO_USER);
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
String json = new Gson().toJson(mockEvent);
assertThat(events).containsExactly(json);
tearDown();
}
@Test
public void storeThenQueryNotVisible() throws Exception {
MockEvent mockEvent = setUpMocks(PROJECT_NOT_VISIBLE_TO_USER);
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
assertThat(events).isEmpty();
tearDown();
}
private MockEvent setUpMocks(boolean isVisible)
throws NoSuchProjectException, IOException {
MockEvent mockEvent = new MockEvent();
ProjectControl pcMock = mock(ProjectControl.class);
CurrentUser userMock = mock(CurrentUser.class);
when(userProviderMock.get()).thenReturn(userMock);
when(pcFactoryMock.controlFor(mockEvent.getProjectNameKey(), userMock))
.thenReturn(pcMock);
when(pcMock.isVisible()).thenReturn(isVisible);
setUpClient();
return mockEvent;
}
@Test(expected = MalformedQueryException.class)
public void throwBadRequestTriggerOnBadQuery() throws Exception {
setUpClient();
String badQuery = "bad query";
store.queryChangeEvents(badQuery);
}
@Test
public void notReturnEventOfNonExistingProject() throws Exception {
MockEvent mockEvent = new MockEvent();
Project.NameKey projectMock = mock(Project.NameKey.class);
CurrentUser userMock = mock(CurrentUser.class);
when(userProviderMock.get()).thenReturn(userMock);
NameKey projectNameKey = mockEvent.getProjectNameKey();
doThrow(new NoSuchProjectException(projectMock)).when(pcFactoryMock)
.controlFor(projectNameKey, userMock);
setUpClient();
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
assertThat(events).isEmpty();
tearDown();
}
@Test
public void notReturnEventWithNoVisibilityInfo() throws Exception {
MockEvent mockEvent = new MockEvent();
CurrentUser userMock = mock(CurrentUser.class);
when(userProviderMock.get()).thenReturn(userMock);
NameKey projectNameKey = mockEvent.getProjectNameKey();
doThrow(new IOException()).when(pcFactoryMock).controlFor(projectNameKey,
userMock);
setUpClient();
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
assertThat(events).isEmpty();
tearDown();
}
@Test
public void retryOnConnectException() throws Exception {
MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(3);
Throwable[] exceptions = new Throwable[3];
Arrays.fill(exceptions, new SQLException(new ConnectException()));
setUpClientMock();
doThrow(exceptions).doNothing().when(eventsDb).storeEvent(mockEvent);
doThrow(exceptions).doNothing().when(eventsDb).queryOne();
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(3)).storeEvent(mockEvent);
verify(localEventsDb).storeEvent(mockEvent);
}
@Test
public void retryOnMessage() throws Exception {
MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(3);
Throwable[] exceptions = new Throwable[3];
Arrays.fill(exceptions, new SQLException(TERM_CONN_MSG));
setUpClientMock();
doThrow(exceptions).doNothing().when(eventsDb).storeEvent(mockEvent);
doThrow(exceptions).doNothing().when(eventsDb).queryOne();
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(3)).storeEvent(mockEvent);
verify(localEventsDb).storeEvent(mockEvent);
}
@Test
public void noRetryOnMessage() throws Exception {
MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(3);
setUpClientMock();
doThrow(new SQLException(MSG)).when(eventsDb).storeEvent(mockEvent);
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(1)).storeEvent(mockEvent);
}
@Test
public void noRetryOnZeroMaxTries() throws Exception {
MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(0);
Throwable[] exceptions = new Throwable[3];
Arrays.fill(exceptions, new SQLException(new ConnectException()));
setUpClientMock();
doThrow(exceptions).doNothing().when(eventsDb).storeEvent(mockEvent);
doThrow(exceptions).doNothing().when(eventsDb).queryOne();
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(1)).storeEvent(mockEvent);
}
@Test(expected = ServiceUnavailableException.class)
public void throwSQLExceptionIfNotOnline() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock();
doThrow(new SQLException(new ConnectException())).when(eventsDb)
.createDBIfNotCreated();
doThrow(new SQLException()).when(eventsDb).queryOne();
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
store.queryChangeEvents(GENERIC_QUERY);
}
@Test
public void restoreFromLocalAndRemoveUnfoundProjectEvents() throws Exception {
MockEvent mockEvent = new MockEvent();
MockEvent mockEvent2 = new MockEvent("proj");
MockEvent mockEvent3 = new MockEvent("unfound");
ProjectControl pc = mock(ProjectControl.class);
NoSuchProjectException e = mock(NoSuchProjectException.class);
CurrentUser userMock = mock(CurrentUser.class);
when(userProviderMock.get()).thenReturn(userMock);
when(pcFactoryMock.controlFor((mockEvent.getProjectNameKey()), userMock))
.thenReturn(pc);
when(pcFactoryMock.controlFor((mockEvent2.getProjectNameKey()), userMock))
.thenReturn(pc);
when(pc.isVisible()).thenReturn(true);
doThrow(e).when(pcFactoryMock).controlFor((mockEvent3.getProjectNameKey()),
userMock);
eventsDb = new SQLClient(TEST_DRIVER, TEST_URL, TEST_OPTIONS);
localEventsDb = new SQLClient(TEST_DRIVER, TEST_LOCAL_URL, TEST_OPTIONS);
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
localEventsDb.createDBIfNotCreated();
localEventsDb.storeEvent(mockEvent);
localEventsDb.storeEvent(mockEvent2);
localEventsDb.storeEvent(mockEvent3);
store.start();
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
Gson gson = new Gson();
String json = gson.toJson(mockEvent);
String json2 = gson.toJson(mockEvent2);
assertThat(events).containsExactly(json, json2);
tearDown();
}
@Test
public void offlineUponStart() throws Exception {
setUpClientMock();
doThrow(new SQLException(new ConnectException())).when(eventsDb)
.createDBIfNotCreated();
doThrow(new SQLException()).when(eventsDb).queryOne();
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
verify(localEventsDb).createDBIfNotCreated();
}
@Test
public void storeLocalOffline() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock();
doThrow(new SQLException(new ConnectException())).when(eventsDb)
.createDBIfNotCreated();
doThrow(new SQLException()).when(eventsDb).queryOne();
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
verify(localEventsDb).storeEvent(mockEvent);
}
@Test
public void storeLocalOfflineAfterNoRetry() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock();
when(cfgMock.getMaxTries()).thenReturn(0);
doThrow(new SQLException(new ConnectException())).when(eventsDb)
.createDBIfNotCreated();
doThrow(new SQLException()).when(eventsDb).queryOne();
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
verify(localEventsDb).storeEvent(mockEvent);
}
/**
* For this test we expect that if we can connect to main database, then we
* should come back online and try setting up again. We just want to make sure
* that restoreEventsFromLocal gets called, so verifying that getLocalDBFile
* is called is sufficient.
*/
@Test
public void testConnectionTask() throws Exception {
eventsDb = new SQLClient(TEST_DRIVER, TEST_URL, TEST_OPTIONS);
localEventsDb = mock(SQLClient.class);
when(localEventsDb.dbExists()).thenReturn(true);
when(localEventsDb.getAll())
.thenReturn(ImmutableList.of(mock(SQLEntry.class)));
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
poolMock.scheduleWithFixedDelay(store.new CheckConnectionTask(), 0, 0,
TimeUnit.MILLISECONDS);
verify(localEventsDb, times(2)).removeOldEvents(0);
}
@Test
public void checkConnectionAndRestoreCopyLocal() throws Exception {
checkConnectionAndRestore(true);
}
@Test
public void checkConnectionAndRestoreNoCopyLocal() throws Exception {
checkConnectionAndRestore(false);
}
private void checkConnectionAndRestore(boolean copy) throws Exception {
MockEvent mockEvent = new MockEvent();
eventsDb = mock(SQLClient.class);
localEventsDb = new SQLClient(TEST_DRIVER, TEST_LOCAL_URL, TEST_OPTIONS);
localEventsDb.createDBIfNotCreated();
localEventsDb.storeEvent(mockEvent);
doThrow(new SQLException(new ConnectException())).doNothing().when(eventsDb)
.createDBIfNotCreated();
if (copy) {
when(cfgMock.getCopyLocal()).thenReturn(true);
}
store = new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
verify(eventsDb).queryOne();
verify(eventsDb).storeEvent(any(String.class), any(Timestamp.class),
any(String.class));
List<SQLEntry> entries = localEventsDb.getAll();
assertThat(entries).isEmpty();
}
public class MockEvent extends ProjectEvent {
public String project = "mock project";
MockEvent() {
super("mock event");
}
MockEvent(String project) {
this();
this.project = project;
}
@Override
public Project.NameKey getProjectNameKey() {
return new Project.NameKey(project);
}
}
class PoolMock extends ScheduledThreadPoolExecutor {
PoolMock() {
super(1);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay, long delay, TimeUnit unit) {
log.info(command.toString());
command.run();
return null;
}
}
}