Replace ChangeJsonTest by an acceptance test

ChangeJsonTest is a unit test that verifies that change messages are
correctly included in the change JSON returned by the change REST
endpoints. For the setup it needs a lot of mocks. The bad thing is that
this test starts failing on code changes which are unrelated to the test
subject, e.g. if a new field is included into the change JSON. Fixing
the test is very painful since it requires to understand the
complicated mock setup and to find out which additional method calls are
now done which are not yet expected by the mocks. To avoid such
problems in future the test is replaced by a new acceptance test which
only tests that change messages are returned correctly and that does
not have any assumptions about which internal methods are invoked to
provide the change messages.

Change-Id: Ia008599f511602f2ac49465eb61b2dabb8b36402
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin 2013-06-12 09:44:36 +02:00 committed by Saša Živkov
parent 28b8ea6f9e
commit af7905cbd7
4 changed files with 191 additions and 233 deletions

View File

@ -0,0 +1,21 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.google.gerrit.acceptance.rest.change;
import java.util.List;
public class ChangeInfo {
List<ChangeMessageInfo> messages;
}

View File

@ -0,0 +1,19 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.google.gerrit.acceptance.rest.change;
public class ChangeMessageInfo {
String message;
}

View File

@ -0,0 +1,151 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.google.gerrit.acceptance.rest.change;
import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
import static com.google.gerrit.acceptance.git.GitUtil.createProject;
import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AccountCreator;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.git.PushOneCommit;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class ChangeMessagesIT extends AbstractDaemonTest {
@Inject
private AccountCreator accounts;
@Inject
private SchemaFactory<ReviewDb> reviewDbProvider;
private TestAccount admin;
private RestSession session;
private Git git;
private ReviewDb db;
@Before
public void setUp() throws Exception {
admin = accounts.create("admin", "admin@example.com", "Administrator",
"Administrators");
session = new RestSession(admin);
initSsh(admin);
Project.NameKey project = new Project.NameKey("p");
SshSession sshSession = new SshSession(admin);
createProject(sshSession, project.get());
git = cloneProject(sshSession.getUrl() + "/" + project.get());
sshSession.close();
db = reviewDbProvider.open();
}
@After
public void cleanup() {
db.close();
}
@Test
public void messagesNotReturnedByDefault() throws GitAPIException,
IOException {
String changeId = createChange();
postMessage(changeId, "Some nits need to be fixed.");
ChangeInfo c = getChange(changeId);
assertNull(c.messages);
}
@Test
public void noMessages() throws GitAPIException,
IOException {
String changeId = createChange();
ChangeInfo c = getChangeWithMessages(changeId);
assertNotNull(c.messages);
assertTrue(c.messages.isEmpty());
}
@Test
public void messagesReturnedInChronologicalOrder() throws GitAPIException,
IOException {
String changeId = createChange();
String firstMessage = "Some nits need to be fixed.";
postMessage(changeId, firstMessage);
String secondMessage = "I like this feature.";
postMessage(changeId, secondMessage);
ChangeInfo c = getChangeWithMessages(changeId);
assertNotNull(c.messages);
assertEquals(2, c.messages.size());
assertMessage(firstMessage, c.messages.get(0).message);
assertMessage(secondMessage, c.messages.get(1).message);
}
private String createChange() throws GitAPIException,
IOException {
PushOneCommit push = new PushOneCommit(db, admin.getIdent());
return push.to(git, "refs/for/master").getChangeId();
}
private ChangeInfo getChange(String changeId) throws IOException {
return getChange(changeId, false);
}
private ChangeInfo getChangeWithMessages(String changeId) throws IOException {
return getChange(changeId, true);
}
private ChangeInfo getChange(String changeId, boolean includeMessages)
throws IOException {
RestResponse r =
session.get("/changes/?q=" + changeId
+ (includeMessages ? "&o=MESSAGES" : ""));
List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
new TypeToken<List<ChangeInfo>>() {}.getType());
return c.get(0);
}
private void assertMessage(String expected, String actual) {
assertEquals("Patch Set 1:\n\n" + expected, actual);
}
private void postMessage(String changeId, String msg) throws IOException {
ReviewInput in = new ReviewInput();
in.message = msg;
session.post("/changes/" + changeId + "/revisions/1/review", in).consume();
}
@SuppressWarnings("unused")
private class ReviewInput {
String message;
}
}

View File

@ -1,233 +0,0 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.google.gerrit.server.change;
import static org.easymock.EasyMock.anyBoolean;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.changes.ListChangesOption;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ChangeAccess;
import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
import com.google.gerrit.reviewdb.server.PatchSetAccess;
import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
import com.google.gerrit.server.change.ChangeJson.ChangeMessageInfo;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.ListResultSet;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Module;
import junit.framework.TestCase;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.eclipse.jgit.lib.Config;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class ChangeJsonTest extends TestCase {
public void testFormatChangeMessages() throws OrmException {
// create mocks
final CurrentUser currentUser = createMock(CurrentUser.class);
final GitRepositoryManager grm = createMock(GitRepositoryManager.class);
final AccountByEmailCache abec = createMock(AccountByEmailCache.class);
final AccountCache ac = createMock(AccountCache.class);
final AccountInfo.Loader.Factory alf =
createMock(AccountInfo.Loader.Factory.class);
final CapabilityControl.Factory ccf =
createMock(CapabilityControl.Factory.class);
final GroupBackend gb = createMock(GroupBackend.class);
final Realm r = createMock(Realm.class);
final PatchListCache plc = createMock(PatchListCache.class);
final ProjectCache pc = createMock(ProjectCache.class);
final Config config = new Config(); // unable to mock
final ReviewDb rdb = createMock(ReviewDb.class);
final ChangeAccess ca = createMock(ChangeAccess.class);
final PatchSetAccess psa = createMock(PatchSetAccess.class);
final PatchSetApprovalAccess psaa =
createMock(PatchSetApprovalAccess.class);
final ChangeMessageAccess cma = createMock(ChangeMessageAccess.class);
AccountInfo.Loader accountLoader = createMock(AccountInfo.Loader.class);
// create ChangeJson instance
Module mod = new Module() {
@Override
public void configure(Binder binder) {
binder.bind(CurrentUser.class).toInstance(currentUser);
binder.bind(GitRepositoryManager.class).toInstance(grm);
binder.bind(AccountByEmailCache.class).toInstance(abec);
binder.bind(AccountCache.class).toInstance(ac);
binder.bind(AccountInfo.Loader.Factory.class).toInstance(alf);
binder.bind(CapabilityControl.Factory.class).toInstance(ccf);
binder.bind(GroupBackend.class).toInstance(gb);
binder.bind(Realm.class).toInstance(r);
binder.bind(PatchListCache.class).toInstance(plc);
binder.bind(ProjectCache.class).toInstance(pc);
binder.bind(ReviewDb.class).toInstance(rdb);
binder.bind(Config.class).annotatedWith(GerritServerConfig.class)
.toInstance(config);
binder.bind(String.class).annotatedWith(CanonicalWebUrl.class)
.toInstance("");
binder.bind(String.class).annotatedWith(AnonymousCowardName.class)
.toInstance("");
}
};
ChangeJson json = Guice.createInjector(mod).getInstance(ChangeJson.class);
// define mock behavior for tests
expect(alf.create(anyBoolean())).andReturn(accountLoader).anyTimes();
Project.NameKey proj = new Project.NameKey("ProjectNameKey");
Branch.NameKey forBranch = new Branch.NameKey(proj, "BranchNameKey");
Change.Key changeKey123 = new Change.Key("ChangeKey123");
Change.Id changeId123 = new Change.Id(123);
Change change123 = new Change(changeKey123, changeId123, null, forBranch);
Change.Key changeKey234 = new Change.Key("ChangeKey234");
Change.Id changeId234 = new Change.Id(234);
Change change234 = new Change(changeKey234, changeId234, null, forBranch);
expect(ca.get(Sets.newHashSet(changeId123)))
.andAnswer(results(Change.class, change123)).anyTimes();
expect(ca.get(changeId123)).andReturn(change123).anyTimes();
expect(ca.get(Sets.newHashSet(changeId234)))
.andAnswer(results(Change.class, change234));
expect(ca.get(changeId234)).andReturn(change234);
expect(rdb.changes()).andReturn(ca).anyTimes();
expect(psa.get(EasyMock.<Iterable<PatchSet.Id>>anyObject()))
.andAnswer(results(PatchSet.class)).anyTimes();
expect(rdb.patchSets()).andReturn(psa).anyTimes();
expect(psaa.byPatchSet(anyObject(PatchSet.Id.class)))
.andAnswer(results(PatchSetApproval.class)).anyTimes();
expect(rdb.patchSetApprovals()).andReturn(psaa).anyTimes();
expect(currentUser.getStarredChanges())
.andReturn(Collections.<Change.Id>emptySet()).anyTimes();
long timeBase = System.currentTimeMillis();
ChangeMessage changeMessage1 =changeMessage(
changeId123, "cm1", 111, timeBase, 1111, "first message");
ChangeMessage changeMessage2 = changeMessage(
changeId123, "cm2", 222, timeBase + 1000, 1111, "second message");
expect(cma.byChange(changeId123))
.andAnswer(results(ChangeMessage.class, changeMessage2, changeMessage1))
.anyTimes();
expect(cma.byChange(changeId234)).andAnswer(results(ChangeMessage.class));
expect(rdb.changeMessages()).andReturn(cma).anyTimes();
expect(accountLoader.get(anyObject(Account.Id.class)))
.andAnswer(accountForId()).anyTimes();
accountLoader.fill();
expectLastCall().anyTimes();
replay(rdb, ca, psa, psaa, alf, currentUser, cma, accountLoader);
// test 1: messages not returned by default
ChangeInfo ci = json.format(new ChangeData(changeId123));
assertNull(ci.messages);
json.addOption(ListChangesOption.MESSAGES);
// test 2: two change messages, in chronological order
ci = json.format(new ChangeData(changeId123));
assertNotNull(ci.messages);
assertEquals(2, ci.messages.size());
Iterator<ChangeMessageInfo> cmis = ci.messages.iterator();
assertEquals(changeMessage1, cmis.next());
assertEquals(changeMessage2, cmis.next());
// test 3: no change messages
ci = json.format(new ChangeData(changeId234));
assertNotNull(ci.messages);
assertEquals(0, ci.messages.size());
}
private static IAnswer<AccountInfo> accountForId() {
return new IAnswer<AccountInfo>() {
@Override
public AccountInfo answer() throws Throwable {
Account.Id id = (Account.Id) EasyMock.getCurrentArguments()[0];
AccountInfo ai = new AccountInfo(id);
return ai;
}};
}
private static <T> IAnswer<ResultSet<T>> results(Class<T> type, T... items) {
final List<T> list = Lists.newArrayList(items);
return new IAnswer<ResultSet<T>>() {
@Override
public ResultSet<T> answer() throws Throwable {
return new ListResultSet<T>(list);
}};
}
private static void assertEquals(ChangeMessage cm, ChangeMessageInfo cmi) {
assertEquals(cm.getPatchSetId().get(), (int) cmi._revisionNumber);
assertEquals(cm.getMessage(), cmi.message);
assertEquals(cm.getKey().get(), cmi.id);
assertEquals(cm.getWrittenOn(), cmi.date);
assertNotNull(cmi.author);
assertEquals(cm.getAuthor(), cmi.author._id);
}
private static ChangeMessage changeMessage(Change.Id changeId,
String uuid, int accountId, long time, int psId, String message) {
ChangeMessage.Key key = new ChangeMessage.Key(changeId, uuid);
Account.Id author = new Account.Id(accountId);
Timestamp updated = new Timestamp(time);
PatchSet.Id ps = new PatchSet.Id(changeId, psId);
ChangeMessage changeMessage = new ChangeMessage(key, author, updated, ps);
changeMessage.setMessage(message);
return changeMessage;
}
}