blob: e4787e1653344f200b9fc04b8826ec5c3c81fe77 [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.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import com.google.gerrit.reviewdb.client.Change.Key;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.events.ChangeEvent;
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 com.ericsson.gerrit.plugins.eventslog.sql.SQLClient;
import com.ericsson.gerrit.plugins.eventslog.sql.SQLEntry;
import com.ericsson.gerrit.plugins.eventslog.sql.SQLStore;
import org.easymock.EasyMock;
import org.easymock.EasyMockSupport;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
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.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SQLStoreTest {
private static final Logger log = LoggerFactory.getLogger(SQLStoreTest.class);
private static final String TEST_URL = "jdbc:h2:mem:";
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";
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 EasyMockSupport easyMock;
private ProjectControl.GenericFactory pcFactoryMock;
private Provider<CurrentUser> userProviderMock;
private EventsLogConfig cfgMock;
private SQLClient eventsDb;
private SQLClient localEventsDb;
private SQLStore store;
private ScheduledThreadPoolExecutor poolMock;
private String path = TEST_URL + TABLE_NAME + ";" + TEST_OPTIONS;
private Connection conn;
private Statement stat;
private List<SQLEntry> results;
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
@SuppressWarnings("unchecked")
@Before
public void setUp() throws SQLException {
conn = DriverManager.getConnection(path);
stat = conn.createStatement();
results = new ArrayList<>();
poolMock = new PoolMock(1);
easyMock = new EasyMockSupport();
pcFactoryMock = easyMock.createNiceMock(ProjectControl.GenericFactory.class);
userProviderMock = easyMock.createNiceMock(Provider.class);
cfgMock = easyMock.createNiceMock(EventsLogConfig.class);
expect(cfgMock.getMaxAge()).andReturn(5);
expect(cfgMock.getLocalStorePath()).andReturn(testFolder.getRoot().toPath()).atLeastOnce();
}
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(boolean reset) throws SQLException {
eventsDb = easyMock.createNiceMock(SQLClient.class);
localEventsDb = easyMock.createNiceMock(SQLClient.class);
expect(localEventsDb.dbExists()).andReturn(true).anyTimes();
if (reset) {
easyMock.resetAll();
}
}
@Test
public void storeThenQueryVisible() throws Exception {
MockEvent mockEvent = new MockEvent();
ProjectControl pcMock = easyMock.createNiceMock(ProjectControl.class);
CurrentUser userMock = easyMock.createNiceMock(CurrentUser.class);
expect(userProviderMock.get()).andStubReturn(userMock);
expect(pcFactoryMock.controlFor(mockEvent.getProjectNameKey(), userMock))
.andStubReturn(pcMock);
expect(pcMock.isVisible()).andStubReturn(true);
easyMock.replayAll();
setUpClient();
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
Gson gson = new Gson();
String json = gson.toJson(mockEvent);
assertThat(events).containsExactly(json);
tearDown();
}
@Test
public void storeThenQueryNotVisible() throws Exception {
MockEvent mockEvent = new MockEvent();
ProjectControl pcMock = easyMock.createNiceMock(ProjectControl.class);
CurrentUser userMock = easyMock.createNiceMock(CurrentUser.class);
expect(userProviderMock.get()).andStubReturn(userMock);
expect(pcFactoryMock.controlFor(mockEvent.getProjectNameKey(), userMock))
.andStubReturn(pcMock);
expect(pcMock.isVisible()).andStubReturn(false);
easyMock.replayAll();
setUpClient();
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
assertThat(events).isEmpty();
tearDown();
}
@Test(expected = MalformedQueryException.class)
public void throwBadRequestTriggerOnBadQuery() throws Exception {
easyMock.replayAll();
setUpClient();
String badQuery = "bad query";
store.queryChangeEvents(badQuery);
easyMock.verifyAll();
}
@Test
public void notReturnEventOfNonExistingProject() throws Exception {
MockEvent mockEvent = new MockEvent();
Project.NameKey projectMock = easyMock.createMock(Project.NameKey.class);
expect(projectMock.get()).andStubReturn(" ");
expect(
pcFactoryMock.controlFor(EasyMock.anyObject(Project.NameKey.class),
EasyMock.anyObject(CurrentUser.class)))
.andThrow(new NoSuchProjectException(projectMock));
easyMock.replayAll();
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();
Project.NameKey projectMock = easyMock.createMock(Project.NameKey.class);
expect(projectMock.get()).andStubReturn(" ");
expect(
pcFactoryMock.controlFor(EasyMock.anyObject(Project.NameKey.class),
EasyMock.anyObject(CurrentUser.class))).andThrow(new IOException());
easyMock.replayAll();
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();
setUpClientMock(false);
EasyMock.reset(eventsDb, localEventsDb);
expect(cfgMock.getMaxTries()).andReturn(3).once();
eventsDb.storeEvent(mockEvent);
expectLastCall().andThrow(new SQLException(new ConnectException()))
.times(3);
expect(localEventsDb.getAll()).andStubReturn(results);
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
easyMock.verifyAll();
}
@Test
public void retryOnMessage() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock(false);
expect(cfgMock.getMaxTries()).andReturn(3).once();
eventsDb.storeEvent(mockEvent);
expectLastCall().andThrow(new SQLException(TERM_CONN_MSG)).times(3);
expect(localEventsDb.getAll()).andStubReturn(results);
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
easyMock.verifyAll();
}
@Test
public void noRetryOnMessage() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock(false);
expect(cfgMock.getMaxTries()).andReturn(3).once();
eventsDb.storeEvent(mockEvent);
expectLastCall().andThrow(new SQLException(MSG)).once();
expect(localEventsDb.getAll()).andReturn(results);
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
easyMock.verifyAll();
}
@Test
public void noRetryOnZeroMaxTries() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock(false);
expect(cfgMock.getMaxTries()).andReturn(0).once();
eventsDb.storeEvent(mockEvent);
expectLastCall().andThrow(new SQLException(new ConnectException())).once();
expect(localEventsDb.getAll()).andStubReturn(results);
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
easyMock.verifyAll();
}
@Test (expected = ServiceUnavailableException.class)
public void throwSQLExceptionIfNotOnline() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock(true);
eventsDb.createDBIfNotCreated();
expectLastCall().andThrow(new SQLException(new ConnectException())).once();
eventsDb.queryOne();
expectLastCall().andThrow(new SQLException());
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
store.queryChangeEvents(GENERIC_QUERY);
easyMock.verifyAll();
}
@Test
public void restoreFromLocalAndRemoveUnfoundProjectEvents() throws Exception {
MockEvent mockEvent = new MockEvent();
MockEvent mockEvent2 = new MockEvent("proj");
MockEvent mockEvent3 = new MockEvent("unfound");
ProjectControl pc = easyMock.createNiceMock(ProjectControl.class);
NoSuchProjectException e =
easyMock.createNiceMock(NoSuchProjectException.class);
expect(
pcFactoryMock.controlFor(EasyMock.anyObject(Project.NameKey.class),
EasyMock.anyObject(CurrentUser.class))).andReturn(pc).times(2);
expect(pc.isVisible()).andReturn(true).times(2);
expect(
pcFactoryMock.controlFor(EasyMock.anyObject(Project.NameKey.class),
EasyMock.anyObject(CurrentUser.class))).andThrow(e);
easyMock.replayAll();
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);
easyMock.verifyAll();
tearDown();
}
@Test
public void offlineUponStart() throws Exception {
setUpClientMock(true);
eventsDb.createDBIfNotCreated();
expectLastCall().andThrow(new SQLException(new ConnectException())).once();
eventsDb.queryOne();
expectLastCall().andThrow(new SQLException());
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
easyMock.verifyAll();
}
@Test
public void storeLocalOffline() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock(true);
eventsDb.createDBIfNotCreated();
expectLastCall().andThrow(new SQLException(new ConnectException())).once();
localEventsDb.storeEvent(mockEvent);
expectLastCall().once();
eventsDb.queryOne();
expectLastCall().andThrow(new SQLException());
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
easyMock.verifyAll();
}
@Test
public void storeLocalOfflineAfterNoRetry() throws Exception {
MockEvent mockEvent = new MockEvent();
setUpClientMock(false);
expect(cfgMock.getMaxTries()).andReturn(0).once();
eventsDb.storeEvent(mockEvent);
expectLastCall().andThrow(new SQLException(new ConnectException())).once();
expect(localEventsDb.getAll()).andStubReturn(results);
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
store.storeEvent(mockEvent);
easyMock.verifyAll();
}
/**
* 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 = easyMock.createMock(SQLClient.class);
expect(localEventsDb.dbExists()).andReturn(true).once();
expect(localEventsDb.getAll()).andReturn(new ArrayList<SQLEntry>());
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
eventsDb.createDBIfNotCreated();
poolMock.scheduleWithFixedDelay(store.new CheckConnectionTask(), 0, 0,
TimeUnit.MILLISECONDS);
easyMock.verifyAll();
}
@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 = easyMock.createNiceMock(SQLClient.class);
localEventsDb = new SQLClient(TEST_DRIVER, TEST_LOCAL_URL, TEST_OPTIONS);
localEventsDb.createDBIfNotCreated();
localEventsDb.storeEvent(mockEvent);
eventsDb.createDBIfNotCreated();
expectLastCall().andThrow(new SQLException(new ConnectException())).once();
eventsDb.queryOne();
expectLastCall().once();
eventsDb.storeEvent(EasyMock.anyString(),
EasyMock.anyObject(Timestamp.class), EasyMock.anyString());
expectLastCall().once();
if (copy) {
testCopyLocal();
}
easyMock.replayAll();
store =
new SQLStore(pcFactoryMock, userProviderMock, cfgMock, eventsDb,
localEventsDb, poolMock);
store.start();
List<SQLEntry> entries = localEventsDb.getAll();
assertThat(entries).isEmpty();
easyMock.verifyAll();
}
private void testCopyLocal() {
expect(cfgMock.getCopyLocal()).andReturn(true).once();
}
public class MockEvent extends ChangeEvent {
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);
}
@Override
public Key getChangeKey() {
return null;
}
@Override
public String getRefName() {
return null;
}
}
class PoolMock extends ScheduledThreadPoolExecutor {
PoolMock(int corePoolSize) {
super(corePoolSize);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay, long delay, TimeUnit unit) {
log.info(command.toString());
command.run();
return null;
}
}
}