| // 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 com.google.common.truth.Truth.assertThat; |
| import static com.google.inject.Scopes.SINGLETON; |
| 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.base.MoreObjects; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| import com.google.gerrit.common.TimeUtil; |
| import com.google.gerrit.extensions.client.Side; |
| import com.google.gerrit.extensions.common.AccountInfo; |
| import com.google.gerrit.extensions.common.CommentInfo; |
| import com.google.gerrit.extensions.registration.DynamicMap; |
| import com.google.gerrit.extensions.restapi.IdString; |
| import com.google.gerrit.extensions.restapi.ResourceNotFoundException; |
| import com.google.gerrit.extensions.restapi.RestView; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.CommentRange; |
| import com.google.gerrit.reviewdb.client.Patch; |
| import com.google.gerrit.reviewdb.client.PatchLineComment; |
| import com.google.gerrit.reviewdb.client.PatchLineComment.Status; |
| import com.google.gerrit.reviewdb.client.PatchSet; |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.reviewdb.client.RevId; |
| import com.google.gerrit.reviewdb.server.PatchLineCommentAccess; |
| import com.google.gerrit.reviewdb.server.ReviewDb; |
| import com.google.gerrit.server.CurrentUser; |
| import com.google.gerrit.server.GerritPersonIdent; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.PatchLineCommentsUtil; |
| import com.google.gerrit.server.account.AccountCache; |
| import com.google.gerrit.server.account.AccountLoader; |
| import com.google.gerrit.server.account.CapabilityControl; |
| import com.google.gerrit.server.account.FakeRealm; |
| import com.google.gerrit.server.account.GroupBackend; |
| import com.google.gerrit.server.account.Realm; |
| import com.google.gerrit.server.config.AllUsersNameProvider; |
| import com.google.gerrit.server.config.AnonymousCowardName; |
| import com.google.gerrit.server.config.AnonymousCowardNameProvider; |
| import com.google.gerrit.server.config.CanonicalWebUrl; |
| import com.google.gerrit.server.config.DisableReverseDnsLookup; |
| import com.google.gerrit.server.config.GerritServerConfig; |
| import com.google.gerrit.server.extensions.events.GitReferenceUpdated; |
| import com.google.gerrit.server.git.GitModule; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.group.SystemGroupBackend; |
| import com.google.gerrit.server.notedb.ChangeUpdate; |
| import com.google.gerrit.server.notedb.NotesMigration; |
| import com.google.gerrit.server.project.ChangeControl; |
| import com.google.gerrit.server.project.ProjectCache; |
| import com.google.gerrit.testutil.ConfigSuite; |
| import com.google.gerrit.testutil.FakeAccountCache; |
| import com.google.gerrit.testutil.InMemoryRepositoryManager; |
| import com.google.gerrit.testutil.TestChanges; |
| import com.google.gwtorm.server.ListResultSet; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.gwtorm.server.ResultSet; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Guice; |
| import com.google.inject.Inject; |
| import com.google.inject.Injector; |
| import com.google.inject.Provides; |
| import com.google.inject.Singleton; |
| import com.google.inject.TypeLiteral; |
| import com.google.inject.util.Providers; |
| |
| import org.easymock.IAnswer; |
| import org.eclipse.jgit.lib.Config; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| import org.junit.runner.RunWith; |
| |
| import java.sql.Timestamp; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TimeZone; |
| |
| @RunWith(ConfigSuite.class) |
| public class CommentsTest { |
| private static final TimeZone TZ = |
| TimeZone.getTimeZone("America/Los_Angeles"); |
| |
| @ConfigSuite.Parameter |
| public Config config; |
| |
| @ConfigSuite.Config |
| @GerritServerConfig |
| public static Config noteDbEnabled() { |
| return NotesMigration.allEnabledConfig(); |
| } |
| |
| @Rule |
| public ExpectedException exception = ExpectedException.none(); |
| |
| private Injector injector; |
| private ReviewDb db; |
| private Project.NameKey project; |
| private Account.Id ownerId; |
| private RevisionResource revRes1; |
| private RevisionResource revRes2; |
| private RevisionResource revRes3; |
| private PatchLineComment plc1; |
| private PatchLineComment plc2; |
| private PatchLineComment plc3; |
| private PatchLineComment plc4; |
| private PatchLineComment plc5; |
| private PatchLineComment plc6; |
| private IdentifiedUser changeOwner; |
| |
| @Inject private AllUsersNameProvider allUsers; |
| @Inject private Comments comments; |
| @Inject private DraftComments drafts; |
| @Inject private GetComment getComment; |
| @Inject private IdentifiedUser.GenericFactory userFactory; |
| @Inject private InMemoryRepositoryManager repoManager; |
| @Inject private NotesMigration migration; |
| @Inject private PatchLineCommentsUtil plcUtil; |
| |
| @Before |
| public void setUp() throws Exception { |
| @SuppressWarnings("unchecked") |
| final DynamicMap<RestView<CommentResource>> commentViews = |
| createMock(DynamicMap.class); |
| final TypeLiteral<DynamicMap<RestView<CommentResource>>> commentViewsType = |
| new TypeLiteral<DynamicMap<RestView<CommentResource>>>() {}; |
| @SuppressWarnings("unchecked") |
| final DynamicMap<RestView<DraftCommentResource>> draftViews = |
| createMock(DynamicMap.class); |
| final TypeLiteral<DynamicMap<RestView<DraftCommentResource>>> draftViewsType = |
| new TypeLiteral<DynamicMap<RestView<DraftCommentResource>>>() {}; |
| |
| final AccountLoader.Factory alf = |
| createMock(AccountLoader.Factory.class); |
| db = createMock(ReviewDb.class); |
| final FakeAccountCache accountCache = new FakeAccountCache(); |
| final PersonIdent serverIdent = new PersonIdent( |
| "Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ); |
| project = new Project.NameKey("test-project"); |
| |
| Account co = new Account(new Account.Id(1), TimeUtil.nowTs()); |
| co.setFullName("Change Owner"); |
| co.setPreferredEmail("change@owner.com"); |
| accountCache.put(co); |
| ownerId = co.getId(); |
| |
| Account ou = new Account(new Account.Id(2), TimeUtil.nowTs()); |
| ou.setFullName("Other Account"); |
| ou.setPreferredEmail("other@account.com"); |
| accountCache.put(ou); |
| Account.Id otherUserId = ou.getId(); |
| |
| AbstractModule mod = new AbstractModule() { |
| @Override |
| protected void configure() { |
| bind(commentViewsType).toInstance(commentViews); |
| bind(draftViewsType).toInstance(draftViews); |
| bind(AccountLoader.Factory.class).toInstance(alf); |
| bind(ReviewDb.class).toInstance(db); |
| bind(Realm.class).to(FakeRealm.class); |
| bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(config); |
| bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null)); |
| install(new GitModule()); |
| bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class); |
| bind(InMemoryRepositoryManager.class) |
| .toInstance(new InMemoryRepositoryManager()); |
| bind(CapabilityControl.Factory.class) |
| .toProvider(Providers.<CapabilityControl.Factory> of(null)); |
| bind(String.class).annotatedWith(AnonymousCowardName.class) |
| .toProvider(AnonymousCowardNameProvider.class); |
| bind(String.class).annotatedWith(CanonicalWebUrl.class) |
| .toInstance("http://localhost:8080/"); |
| bind(Boolean.class).annotatedWith(DisableReverseDnsLookup.class) |
| .toInstance(Boolean.FALSE); |
| bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON); |
| bind(AccountCache.class).toInstance(accountCache); |
| bind(GitReferenceUpdated.class) |
| .toInstance(GitReferenceUpdated.DISABLED); |
| bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class) |
| .toInstance(serverIdent); |
| } |
| |
| @Provides |
| @Singleton |
| CurrentUser getUser(IdentifiedUser.GenericFactory userFactory) { |
| return userFactory.create(ownerId); |
| } |
| }; |
| |
| injector = Guice.createInjector(mod); |
| injector.injectMembers(this); |
| |
| repoManager.createRepository(project); |
| changeOwner = userFactory.create(ownerId); |
| IdentifiedUser otherUser = userFactory.create(otherUserId); |
| |
| AccountLoader accountLoader = createMock(AccountLoader.class); |
| accountLoader.fill(); |
| expectLastCall().anyTimes(); |
| expect(accountLoader.get(ownerId)) |
| .andReturn(new AccountInfo(ownerId.get())).anyTimes(); |
| expect(accountLoader.get(otherUserId)) |
| .andReturn(new AccountInfo(otherUserId.get())).anyTimes(); |
| expect(alf.create(true)).andReturn(accountLoader).anyTimes(); |
| replay(accountLoader, alf); |
| |
| repoManager.createRepository(allUsers.get()); |
| |
| PatchLineCommentAccess plca = createMock(PatchLineCommentAccess.class); |
| expect(db.patchComments()).andReturn(plca).anyTimes(); |
| |
| Change change1 = newChange(); |
| PatchSet.Id psId1 = new PatchSet.Id(change1.getId(), 1); |
| PatchSet ps1 = new PatchSet(psId1); |
| PatchSet.Id psId2 = new PatchSet.Id(change1.getId(), 2); |
| PatchSet ps2 = new PatchSet(psId2); |
| |
| Change change2 = newChange(); |
| PatchSet.Id psId3 = new PatchSet.Id(change2.getId(), 1); |
| PatchSet ps3 = new PatchSet(psId3); |
| |
| long timeBase = TimeUtil.roundToSecond(TimeUtil.nowTs()).getTime(); |
| plc1 = newPatchLineComment(psId1, "Comment1", null, |
| "FileOne.txt", Side.REVISION, 3, ownerId, timeBase, |
| "First Comment", new CommentRange(1, 2, 3, 4)); |
| plc1.setRevId(new RevId("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd")); |
| plc2 = newPatchLineComment(psId1, "Comment2", "Comment1", |
| "FileOne.txt", Side.REVISION, 3, otherUserId, timeBase + 1000, |
| "Reply to First Comment", new CommentRange(1, 2, 3, 4)); |
| plc2.setRevId(new RevId("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd")); |
| plc3 = newPatchLineComment(psId1, "Comment3", "Comment1", |
| "FileOne.txt", Side.PARENT, 3, ownerId, timeBase + 2000, |
| "First Parent Comment", new CommentRange(1, 2, 3, 4)); |
| plc3.setRevId(new RevId("cdefcdefcdefcdefcdefcdefcdefcdefcdefcdef")); |
| plc4 = newPatchLineComment(psId2, "Comment4", null, "FileOne.txt", |
| Side.REVISION, 3, ownerId, timeBase + 3000, "Second Comment", |
| new CommentRange(1, 2, 3, 4), Status.DRAFT); |
| plc4.setRevId(new RevId("bcdebcdebcdebcdebcdebcdebcdebcdebcdebcde")); |
| plc5 = newPatchLineComment(psId2, "Comment5", null, "FileOne.txt", |
| Side.REVISION, 5, ownerId, timeBase + 4000, "Third Comment", |
| new CommentRange(3, 4, 5, 6), Status.DRAFT); |
| plc5.setRevId(new RevId("bcdebcdebcdebcdebcdebcdebcdebcdebcdebcde")); |
| plc6 = newPatchLineComment(psId3, "Comment6", null, "FileOne.txt", |
| Side.REVISION, 5, ownerId, timeBase + 5000, "Sixth Comment", |
| new CommentRange(3, 4, 5, 6), Status.DRAFT); |
| plc6.setRevId(new RevId("1234123412341234123412341234123412341234")); |
| |
| List<PatchLineComment> commentsByOwner = Lists.newArrayList(); |
| commentsByOwner.add(plc1); |
| commentsByOwner.add(plc3); |
| List<PatchLineComment> commentsByReviewer = Lists.newArrayList(); |
| commentsByReviewer.add(plc2); |
| List<PatchLineComment> drafts1 = Lists.newArrayList(); |
| drafts1.add(plc4); |
| drafts1.add(plc5); |
| List<PatchLineComment> drafts2 = Lists.newArrayList(); |
| drafts2.add(plc6); |
| |
| plca.upsert(commentsByOwner); |
| expectLastCall().anyTimes(); |
| plca.upsert(commentsByReviewer); |
| expectLastCall().anyTimes(); |
| plca.upsert(drafts1); |
| expectLastCall().anyTimes(); |
| plca.upsert(drafts2); |
| expectLastCall().anyTimes(); |
| |
| expect(plca.publishedByPatchSet(psId1)) |
| .andAnswer(results(plc1, plc2, plc3)).anyTimes(); |
| expect(plca.publishedByPatchSet(psId2)) |
| .andAnswer(results()).anyTimes(); |
| expect(plca.draftByPatchSetAuthor(psId1, ownerId)) |
| .andAnswer(results()).anyTimes(); |
| expect(plca.draftByPatchSetAuthor(psId2, ownerId)) |
| .andAnswer(results(plc4, plc5)).anyTimes(); |
| expect(plca.byChange(change1.getId())) |
| .andAnswer(results(plc1, plc2, plc3, plc4, plc5)).anyTimes(); |
| expect(plca.draftByAuthor(ownerId)) |
| .andAnswer(results(plc4, plc5, plc6)).anyTimes(); |
| replay(db, plca); |
| |
| ChangeUpdate update = newUpdate(change1, changeOwner); |
| update.setPatchSetId(psId1); |
| plcUtil.upsertComments(db, update, commentsByOwner); |
| update.commit(); |
| |
| update = newUpdate(change1, otherUser); |
| update.setPatchSetId(psId1); |
| plcUtil.upsertComments(db, update, commentsByReviewer); |
| update.commit(); |
| |
| update = newUpdate(change1, changeOwner); |
| update.setPatchSetId(psId2); |
| plcUtil.upsertComments(db, update, drafts1); |
| update.commit(); |
| |
| update = newUpdate(change2, changeOwner); |
| update.setPatchSetId(psId3); |
| plcUtil.upsertComments(db, update, drafts2); |
| update.commit(); |
| |
| ChangeControl ctl = stubChangeControl(change1); |
| revRes1 = new RevisionResource(new ChangeResource(ctl), ps1); |
| revRes2 = new RevisionResource(new ChangeResource(ctl), ps2); |
| revRes3 = new RevisionResource(new ChangeResource(stubChangeControl(change2)), ps3); |
| } |
| |
| private ChangeControl stubChangeControl(Change c) throws OrmException { |
| return TestChanges.stubChangeControl( |
| repoManager, migration, c, allUsers, changeOwner); |
| } |
| |
| private Change newChange() { |
| return TestChanges.newChange(project, changeOwner.getAccountId()); |
| } |
| |
| private ChangeUpdate newUpdate(Change c, final IdentifiedUser user) throws Exception { |
| return TestChanges.newUpdate( |
| injector, repoManager, migration, c, allUsers, user); |
| } |
| |
| @Test |
| public void testListComments() throws Exception { |
| // test ListComments for patch set 1 |
| assertListComments(revRes1, ImmutableMap.of( |
| "FileOne.txt", Lists.newArrayList(plc3, plc1, plc2))); |
| |
| // test ListComments for patch set 2 |
| assertListComments(revRes2, |
| Collections.<String, List<PatchLineComment>>emptyMap()); |
| } |
| |
| @Test |
| public void testGetCommentExisting() throws Exception { |
| // test GetComment for existing comment |
| String uuid = plc1.getKey().get(); |
| CommentResource commentRes = comments.parse(revRes1, IdString.fromUrl(uuid)); |
| CommentInfo actual = getComment.apply(commentRes); |
| assertComment(plc1, actual, true); |
| } |
| |
| @Test |
| public void testGetCommentNotExisting() throws Exception { |
| // test GetComment for non-existent comment |
| exception.expect(ResourceNotFoundException.class); |
| comments.parse(revRes1, IdString.fromUrl("BadComment")); |
| } |
| |
| @Test |
| public void testListDrafts() throws Exception { |
| // test ListDrafts for patch set 1 |
| assertListDrafts(revRes1, |
| Collections.<String, List<PatchLineComment>> emptyMap()); |
| |
| // test ListDrafts for patch set 2 |
| assertListDrafts(revRes2, ImmutableMap.of( |
| "FileOne.txt", Lists.newArrayList(plc4, plc5))); |
| } |
| |
| @Test |
| public void testPatchLineCommentsUtilByCommentStatus() throws OrmException { |
| assertThat(plcUtil.publishedByChange(db, revRes2.getNotes())) |
| .containsExactly(plc3, plc1, plc2).inOrder(); |
| assertThat(plcUtil.draftByChange(db, revRes2.getNotes())) |
| .containsExactly(plc4, plc5).inOrder(); |
| } |
| |
| @Test |
| public void testPatchLineCommentsUtilDraftByChangeAuthor() throws Exception { |
| assertThat(plcUtil.draftByChangeAuthor(db, revRes1.getNotes(), ownerId)) |
| .containsExactly(plc4, plc5).inOrder(); |
| assertThat(plcUtil.draftByChangeAuthor(db, revRes3.getNotes(), ownerId)) |
| .containsExactly(plc6); |
| } |
| |
| private static IAnswer<ResultSet<PatchLineComment>> results( |
| final PatchLineComment... comments) { |
| return new IAnswer<ResultSet<PatchLineComment>>() { |
| @Override |
| public ResultSet<PatchLineComment> answer() throws Throwable { |
| return new ListResultSet<>(Lists.newArrayList(comments)); |
| } |
| }; |
| } |
| |
| private void assertListComments(RevisionResource res, |
| Map<String, ? extends List<PatchLineComment>> expected) throws Exception { |
| assertCommentMap(comments.list().apply(res), expected, true); |
| } |
| |
| private void assertListDrafts(RevisionResource res, |
| Map<String, ? extends List<PatchLineComment>> expected) throws Exception { |
| assertCommentMap(drafts.list().apply(res), expected, false); |
| } |
| |
| private void assertCommentMap(Map<String, List<CommentInfo>> actual, |
| Map<String, ? extends List<PatchLineComment>> expected, |
| boolean isPublished) { |
| assertThat(actual.keySet()).containsExactlyElementsIn(expected.keySet()); |
| for (Map.Entry<String, List<CommentInfo>> entry : actual.entrySet()) { |
| List<CommentInfo> actualList = entry.getValue(); |
| List<PatchLineComment> expectedList = expected.get(entry.getKey()); |
| assertThat(actualList).hasSize(expectedList.size()); |
| for (int i = 0; i < expectedList.size(); i++) { |
| assertComment(expectedList.get(i), actualList.get(i), isPublished); |
| } |
| } |
| } |
| |
| private static void assertComment(PatchLineComment plc, CommentInfo ci, |
| boolean isPublished) { |
| assertThat(ci.id).isEqualTo(plc.getKey().get()); |
| assertThat(ci.inReplyTo).isEqualTo(plc.getParentUuid()); |
| assertThat(ci.message).isEqualTo(plc.getMessage()); |
| if (isPublished) { |
| assertThat(ci.author).isNotNull(); |
| assertThat(new Account.Id(ci.author._accountId)) |
| .isEqualTo(plc.getAuthor()); |
| } |
| assertThat(ci.line).isEqualTo(plc.getLine()); |
| assertThat(MoreObjects.firstNonNull(ci.side, Side.REVISION)) |
| .isEqualTo(plc.getSide() == 0 ? Side.PARENT : Side.REVISION); |
| assertThat(TimeUtil.roundToSecond(ci.updated)) |
| .isEqualTo(TimeUtil.roundToSecond(plc.getWrittenOn())); |
| assertThat(ci.updated).isEqualTo(plc.getWrittenOn()); |
| assertThat(ci.range.startLine).isEqualTo(plc.getRange().getStartLine()); |
| assertThat(ci.range.startCharacter) |
| .isEqualTo(plc.getRange().getStartCharacter()); |
| assertThat(ci.range.endLine).isEqualTo(plc.getRange().getEndLine()); |
| assertThat(ci.range.endCharacter) |
| .isEqualTo(plc.getRange().getEndCharacter()); |
| } |
| |
| private static PatchLineComment newPatchLineComment(PatchSet.Id psId, |
| String uuid, String inReplyToUuid, String filename, Side side, int line, |
| Account.Id authorId, long millis, String message, CommentRange range, |
| PatchLineComment.Status status) { |
| Patch.Key p = new Patch.Key(psId, filename); |
| PatchLineComment.Key id = new PatchLineComment.Key(p, uuid); |
| PatchLineComment plc = |
| new PatchLineComment(id, line, authorId, inReplyToUuid, TimeUtil.nowTs()); |
| plc.setMessage(message); |
| plc.setRange(range); |
| plc.setSide(side == Side.PARENT ? (short) 0 : (short) 1); |
| plc.setStatus(status); |
| plc.setWrittenOn(new Timestamp(millis)); |
| return plc; |
| } |
| |
| private static PatchLineComment newPatchLineComment(PatchSet.Id psId, |
| String uuid, String inReplyToUuid, String filename, Side side, int line, |
| Account.Id authorId, long millis, String message, CommentRange range) { |
| return newPatchLineComment(psId, uuid, inReplyToUuid, filename, side, line, |
| authorId, millis, message, range, Status.PUBLISHED); |
| } |
| } |