| // Copyright (C) 2015 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.api.group; | |
| import static com.google.common.collect.ImmutableList.toImmutableList; | |
| import static com.google.common.collect.MoreCollectors.onlyElement; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.common.truth.Truth8.assertThat; | |
| import static com.google.gerrit.acceptance.GitUtil.deleteRef; | |
| import static com.google.gerrit.acceptance.GitUtil.fetch; | |
| import static com.google.gerrit.acceptance.api.group.GroupAssert.assertGroupInfo; | |
| import static com.google.gerrit.acceptance.rest.account.AccountAssert.assertAccountInfos; | |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; | |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability; | |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel; | |
| import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; | |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; | |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; | |
| import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction; | |
| import static com.google.gerrit.truth.MapSubject.assertThatMap; | |
| import static java.lang.annotation.ElementType.METHOD; | |
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
| import static java.util.stream.Collectors.toList; | |
| import static java.util.stream.Collectors.toMap; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.Iterables; | |
| import com.google.common.truth.Correspondence; | |
| import com.google.common.util.concurrent.AtomicLongMap; | |
| import com.google.gerrit.acceptance.AbstractDaemonTest; | |
| import com.google.gerrit.acceptance.ExtensionRegistry; | |
| import com.google.gerrit.acceptance.ExtensionRegistry.Registration; | |
| import com.google.gerrit.acceptance.GitUtil; | |
| import com.google.gerrit.acceptance.NoHttpd; | |
| import com.google.gerrit.acceptance.ProjectResetter; | |
| import com.google.gerrit.acceptance.PushOneCommit; | |
| import com.google.gerrit.acceptance.Sandboxed; | |
| import com.google.gerrit.acceptance.TestAccount; | |
| import com.google.gerrit.acceptance.UseClockStep; | |
| import com.google.gerrit.acceptance.config.GerritConfig; | |
| import com.google.gerrit.acceptance.testsuite.account.AccountOperations; | |
| import com.google.gerrit.acceptance.testsuite.group.GroupOperations; | |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; | |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; | |
| import com.google.gerrit.auth.ldap.FakeLdapGroupBackend; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.common.data.GlobalCapability; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.entities.AccountGroup; | |
| import com.google.gerrit.entities.GroupReference; | |
| import com.google.gerrit.entities.InternalGroup; | |
| import com.google.gerrit.entities.LabelId; | |
| import com.google.gerrit.entities.Permission; | |
| import com.google.gerrit.entities.Project; | |
| import com.google.gerrit.entities.RefNames; | |
| import com.google.gerrit.extensions.api.changes.ReviewInput; | |
| import com.google.gerrit.extensions.api.groups.GroupApi; | |
| import com.google.gerrit.extensions.api.groups.GroupInput; | |
| import com.google.gerrit.extensions.api.groups.Groups.ListRequest; | |
| import com.google.gerrit.extensions.common.AccountInfo; | |
| import com.google.gerrit.extensions.common.GroupAuditEventInfo; | |
| import com.google.gerrit.extensions.common.GroupAuditEventInfo.GroupMemberAuditEventInfo; | |
| import com.google.gerrit.extensions.common.GroupAuditEventInfo.UserMemberAuditEventInfo; | |
| import com.google.gerrit.extensions.common.GroupInfo; | |
| import com.google.gerrit.extensions.common.GroupOptionsInfo; | |
| import com.google.gerrit.extensions.events.GroupIndexedListener; | |
| import com.google.gerrit.extensions.registration.DynamicSet; | |
| import com.google.gerrit.extensions.restapi.AuthException; | |
| import com.google.gerrit.extensions.restapi.BadRequestException; | |
| import com.google.gerrit.extensions.restapi.ResourceConflictException; | |
| import com.google.gerrit.extensions.restapi.ResourceNotFoundException; | |
| import com.google.gerrit.extensions.restapi.UnprocessableEntityException; | |
| import com.google.gerrit.extensions.restapi.Url; | |
| import com.google.gerrit.server.ServerInitiated; | |
| import com.google.gerrit.server.account.GroupBackend; | |
| import com.google.gerrit.server.account.GroupIncludeCache; | |
| import com.google.gerrit.server.account.GroupsSnapshotReader; | |
| import com.google.gerrit.server.account.ServiceUserClassifier; | |
| import com.google.gerrit.server.group.PeriodicGroupIndexer; | |
| import com.google.gerrit.server.group.SystemGroupBackend; | |
| import com.google.gerrit.server.group.db.GroupDelta; | |
| import com.google.gerrit.server.group.db.Groups; | |
| import com.google.gerrit.server.group.db.GroupsConsistencyChecker; | |
| import com.google.gerrit.server.group.db.GroupsUpdate; | |
| import com.google.gerrit.server.group.db.InternalGroupCreation; | |
| import com.google.gerrit.server.index.group.GroupIndexer; | |
| import com.google.gerrit.server.index.group.StalenessChecker; | |
| import com.google.gerrit.server.notedb.Sequences; | |
| import com.google.gerrit.server.util.MagicBranch; | |
| import com.google.gerrit.server.util.time.TimeUtil; | |
| import com.google.gerrit.testing.GerritJUnit.ThrowingRunnable; | |
| import com.google.gerrit.truth.NullAwareCorrespondence; | |
| import com.google.inject.AbstractModule; | |
| import com.google.inject.Inject; | |
| import com.google.inject.Module; | |
| import java.io.IOException; | |
| import java.lang.annotation.Retention; | |
| import java.lang.annotation.Target; | |
| import java.sql.Timestamp; | |
| import java.time.Instant; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.List; | |
| import java.util.Locale; | |
| import java.util.Map; | |
| import java.util.stream.Stream; | |
| import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; | |
| import org.eclipse.jgit.junit.TestRepository; | |
| import org.eclipse.jgit.lib.CommitBuilder; | |
| import org.eclipse.jgit.lib.Config; | |
| import org.eclipse.jgit.lib.Constants; | |
| import org.eclipse.jgit.lib.ObjectId; | |
| import org.eclipse.jgit.lib.ObjectInserter; | |
| import org.eclipse.jgit.lib.PersonIdent; | |
| import org.eclipse.jgit.lib.Ref; | |
| import org.eclipse.jgit.lib.RefUpdate; | |
| import org.eclipse.jgit.lib.Repository; | |
| import org.eclipse.jgit.revwalk.RevCommit; | |
| import org.eclipse.jgit.revwalk.RevWalk; | |
| import org.eclipse.jgit.transport.PushResult; | |
| import org.eclipse.jgit.transport.RemoteRefUpdate; | |
| import org.junit.After; | |
| import org.junit.Test; | |
| @NoHttpd | |
| @UseClockStep | |
| public class GroupsIT extends AbstractDaemonTest { | |
| @Inject @ServerInitiated private GroupsUpdate groupsUpdate; | |
| @Inject private AccountOperations accountOperations; | |
| @Inject private GroupIncludeCache groupIncludeCache; | |
| @Inject private GroupIndexer groupIndexer; | |
| @Inject private GroupOperations groupOperations; | |
| @Inject private Groups groups; | |
| @Inject private GroupsConsistencyChecker consistencyChecker; | |
| @Inject private PeriodicGroupIndexer slaveGroupIndexer; | |
| @Inject private ProjectOperations projectOperations; | |
| @Inject private RequestScopeOperations requestScopeOperations; | |
| @Inject private Sequences seq; | |
| @Inject private StalenessChecker stalenessChecker; | |
| @Inject private ExtensionRegistry extensionRegistry; | |
| @Inject private GroupsSnapshotReader groupsSnapshotReader; | |
| @Override | |
| public Module createModule() { | |
| return new AbstractModule() { | |
| @Override | |
| protected void configure() { | |
| /** Binding a {@link FakeLdapGroupBackend} to test adding external groups * */ | |
| DynamicSet.bind(binder(), GroupBackend.class).to(FakeLdapGroupBackend.class); | |
| } | |
| }; | |
| } | |
| @After | |
| public void consistencyCheck() throws Exception { | |
| if (description.getAnnotation(IgnoreGroupInconsistencies.class) == null) { | |
| assertThat(consistencyChecker.check()).isEmpty(); | |
| } | |
| } | |
| @Override | |
| protected ProjectResetter.Config resetProjects() { | |
| // Don't reset All-Users since deleting users makes groups inconsistent (e.g. groups would | |
| // contain members that no longer exist) and as result of this the group consistency checker | |
| // that is executed after each test would fail. | |
| return new ProjectResetter.Config().reset(allProjects, RefNames.REFS_CONFIG); | |
| } | |
| @Test | |
| public void systemGroupCanBeRetrievedFromIndex() throws Exception { | |
| List<GroupInfo> groupInfos = gApi.groups().query("name:Administrators").get(); | |
| assertThat(groupInfos).isNotEmpty(); | |
| } | |
| @Test | |
| public void addToNonExistingGroup_NotFound() throws Exception { | |
| assertThrows( | |
| ResourceNotFoundException.class, | |
| () -> gApi.groups().id("non-existing").addMembers("admin")); | |
| } | |
| @Test | |
| public void removeFromNonExistingGroup_NotFound() throws Exception { | |
| assertThrows( | |
| ResourceNotFoundException.class, | |
| () -> gApi.groups().id("non-existing").removeMembers("admin")); | |
| } | |
| @Test | |
| public void addRemoveMember() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| gApi.groups().id(group.get()).addMembers("user1"); | |
| assertMembers(group.get(), user); | |
| gApi.groups().id(group.get()).removeMembers("user1"); | |
| ImmutableSet<Account.Id> members = groupOperations.group(group).get().members(); | |
| assertThat(members).isEmpty(); | |
| } | |
| @Test | |
| public void addExternalGroups() throws Exception { | |
| AccountGroup.UUID group1 = groupOperations.newGroup().create(); | |
| AccountGroup.UUID group2 = groupOperations.newGroup().create(); | |
| String g1RefName = RefNames.refsGroups(group1); | |
| String g2RefName = RefNames.refsGroups(group2); | |
| gApi.groups().id(group1.get()).addGroups("ldap:external_g1"); | |
| gApi.groups().id(group2.get()).addGroups("ldap:external_g2"); | |
| assertThat(groupIncludeCache.allExternalMembers()) | |
| .containsAtLeastElementsIn( | |
| ImmutableList.of( | |
| AccountGroup.UUID.parse("ldap:external_g1"), | |
| AccountGroup.UUID.parse("ldap:external_g2"))); | |
| assertThat(groupIncludeCache.parentGroupsOf(AccountGroup.UUID.parse("ldap:external_g1"))) | |
| .containsExactly(group1); | |
| assertThat(groupIncludeCache.parentGroupsOf(AccountGroup.UUID.parse("ldap:external_g2"))) | |
| .containsExactly(group2); | |
| GroupsSnapshotReader.Snapshot snapshot = groupsSnapshotReader.getSnapshot(); | |
| gApi.groups().id(group1.get()).removeGroups("ldap:external_g1"); | |
| GroupsSnapshotReader.Snapshot newSnapshot = groupsSnapshotReader.getSnapshot(); | |
| /** Make sure groups snapshots are consistent */ | |
| ObjectId g1ObjectId = getObjectIdFromSnapshot(snapshot, g1RefName); | |
| ObjectId g2ObjectId = getObjectIdFromSnapshot(snapshot, g2RefName); | |
| assertThat(snapshot.hash()).isNotEqualTo(newSnapshot.hash()); | |
| assertThat(g1ObjectId).isNotEqualTo(getObjectIdFromSnapshot(newSnapshot, g1RefName)); | |
| assertThat(g2ObjectId).isEqualTo(getObjectIdFromSnapshot(newSnapshot, g2RefName)); | |
| assertThat(snapshot.groupsRefs().stream().map(Ref::getName).collect(toList())) | |
| .containsAtLeastElementsIn(ImmutableList.of(g1RefName, g2RefName)); | |
| assertThat(newSnapshot.groupsRefs().stream().map(Ref::getName).collect(toList())) | |
| .containsAtLeastElementsIn(ImmutableList.of(g1RefName, g2RefName)); | |
| /** GroupIncludeCache should return ldap:external_g2 only */ | |
| assertThat(groupIncludeCache.allExternalMembers()) | |
| .contains(AccountGroup.UUID.parse("ldap:external_g2")); | |
| /** Testing groups.getExternalGroups() with the old Snapshot */ | |
| assertThat(groups.getExternalGroups(snapshot.groupsRefs())) | |
| .containsAtLeastElementsIn( | |
| ImmutableList.of( | |
| AccountGroup.UUID.parse("ldap:external_g1"), | |
| AccountGroup.UUID.parse("ldap:external_g2"))); | |
| } | |
| private ObjectId getObjectIdFromSnapshot(GroupsSnapshotReader.Snapshot snapshot, String refName) { | |
| return snapshot.groupsRefs().stream() | |
| .filter(r -> r.getName().equals(refName)) | |
| .map(Ref::getObjectId) | |
| .collect(onlyElement()); | |
| } | |
| @Test | |
| public void removeMember_nullInMemberInputDoesNotCauseFailure() throws Exception { | |
| AccountGroup.UUID group = | |
| groupOperations.newGroup().addMember(admin.id()).addMember(user.id()).create(); | |
| gApi.groups().id(group.get()).removeMembers(user.id().toString(), null); | |
| ImmutableSet<Account.Id> members = groupOperations.group(group).get().members(); | |
| assertThat(members).containsExactly(admin.id()); | |
| } | |
| @Test | |
| public void removeMember_emptyStringInMemberInputDoesNotCauseFailure() throws Exception { | |
| AccountGroup.UUID group = | |
| groupOperations.newGroup().addMember(admin.id()).addMember(user.id()).create(); | |
| gApi.groups().id(group.get()).removeMembers(user.id().toString(), ""); | |
| ImmutableSet<Account.Id> members = groupOperations.group(group).get().members(); | |
| assertThat(members).containsExactly(admin.id()); | |
| } | |
| @Test | |
| public void cachedGroupsForMemberAreUpdatedOnMemberAdditionAndRemoval() throws Exception { | |
| String username = name("user"); | |
| Account.Id accountId = accountOperations.newAccount().username(username).create(); | |
| // Fill the cache for the observed account. | |
| groupIncludeCache.getGroupsWithMember(accountId); | |
| AccountGroup.UUID groupUuid = groupOperations.newGroup().create(); | |
| gApi.groups().id(groupUuid.get()).addMembers(username); | |
| Collection<AccountGroup.UUID> groupsWithMemberAfterAddition = | |
| groupIncludeCache.getGroupsWithMember(accountId); | |
| assertThat(groupsWithMemberAfterAddition).contains(groupUuid); | |
| gApi.groups().id(groupUuid.get()).removeMembers(username); | |
| Collection<AccountGroup.UUID> groupsWithMemberAfterRemoval = | |
| groupIncludeCache.getGroupsWithMember(accountId); | |
| assertThat(groupsWithMemberAfterRemoval).doesNotContain(groupUuid); | |
| } | |
| @Test | |
| public void cachedGroupByNameIsUpdatedOnCreation() throws Exception { | |
| String newGroupName = name("newGroup"); | |
| AccountGroup.NameKey nameKey = AccountGroup.nameKey(newGroupName); | |
| assertThat(groupCache.get(nameKey)).isEmpty(); | |
| gApi.groups().create(newGroupName); | |
| assertThat(groupCache.get(nameKey)).isPresent(); | |
| } | |
| @Test | |
| public void addExistingMember_OK() throws Exception { | |
| String g = "Administrators"; | |
| assertMembers(g, admin); | |
| gApi.groups().id("Administrators").addMembers("admin"); | |
| assertMembers(g, admin); | |
| } | |
| @Test | |
| public void addNonExistingMember_UnprocessableEntity() throws Exception { | |
| assertThrows( | |
| UnprocessableEntityException.class, | |
| () -> gApi.groups().id("Administrators").addMembers("non-existing")); | |
| } | |
| @Test | |
| public void addMultipleMembers() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| String u1 = name("u1"); | |
| accountOperations.newAccount().username(u1).create(); | |
| String u2 = name("u2"); | |
| accountOperations.newAccount().username(u2).create(); | |
| gApi.groups().id(group.get()).addMembers(u1, u2); | |
| List<AccountInfo> members = gApi.groups().id(group.get()).members(); | |
| assertThat(members) | |
| .comparingElementsUsing(getAccountToUsernameCorrespondence()) | |
| .containsExactly(u1, u2); | |
| } | |
| @Test | |
| public void membersWithAtSignInUsernameCanBeAdded() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| String usernameWithAt = name("u1@something"); | |
| accountOperations.newAccount().username(usernameWithAt).create(); | |
| gApi.groups().id(group.get()).addMembers(usernameWithAt); | |
| List<AccountInfo> members = gApi.groups().id(group.get()).members(); | |
| assertThat(members) | |
| .comparingElementsUsing(getAccountToUsernameCorrespondence()) | |
| .containsExactly(usernameWithAt); | |
| } | |
| @Test | |
| public void membersWithAtSignInUsernameAreNotConfusedWithSimilarUsernames() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| String usernameWithAt = name("u1@something"); | |
| accountOperations.newAccount().username(usernameWithAt).create(); | |
| String usernameWithoutAt = name("u1something"); | |
| accountOperations.newAccount().username(usernameWithoutAt).create(); | |
| String usernameOnlyPrefix = name("u1"); | |
| accountOperations.newAccount().username(usernameOnlyPrefix).create(); | |
| String usernameOnlySuffix = name("something"); | |
| accountOperations.newAccount().username(usernameOnlySuffix).create(); | |
| gApi.groups() | |
| .id(group.get()) | |
| .addMembers(usernameWithAt, usernameWithoutAt, usernameOnlyPrefix, usernameOnlySuffix); | |
| List<AccountInfo> members = gApi.groups().id(group.get()).members(); | |
| assertThat(members) | |
| .comparingElementsUsing(getAccountToUsernameCorrespondence()) | |
| .containsExactly(usernameWithAt, usernameWithoutAt, usernameOnlyPrefix, usernameOnlySuffix); | |
| } | |
| @Test | |
| public void includeRemoveGroup() throws Exception { | |
| AccountGroup.UUID parent = groupOperations.newGroup().create(); | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| gApi.groups().id(parent.get()).addGroups(group.get()); | |
| assertThat(groupOperations.group(parent).get().subgroups()).containsExactly(group); | |
| gApi.groups().id(parent.get()).removeGroups(group.get()); | |
| assertThat(groupOperations.group(parent).get().subgroups()).isEmpty(); | |
| } | |
| @Test | |
| public void includeExternalGroup() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| String subgroupUuid = SystemGroupBackend.REGISTERED_USERS.get(); | |
| gApi.groups().id(group.get()).addGroups(subgroupUuid); | |
| List<GroupInfo> subgroups = gApi.groups().id(group.get()).includedGroups(); | |
| assertThat(subgroups).hasSize(1); | |
| assertThat(subgroups.get(0).id).isEqualTo(subgroupUuid.replace(":", "%3A")); | |
| assertThat(subgroups.get(0).name).isEqualTo("Registered Users"); | |
| assertThat(subgroups.get(0).groupId).isNull(); | |
| List<? extends GroupAuditEventInfo> auditEvents = gApi.groups().id(group.get()).auditLog(); | |
| assertThat(auditEvents).hasSize(1); | |
| assertSubgroupAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.ADD_GROUP, admin.id(), "Registered Users"); | |
| } | |
| @Test | |
| public void includeExistingGroup_OK() throws Exception { | |
| AccountGroup.UUID parent = groupOperations.newGroup().create(); | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| groupOperations.group(parent).forUpdate().addSubgroup(group); | |
| gApi.groups().id(parent.get()).addGroups(group.get()); | |
| ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(parent).get().subgroups(); | |
| assertThat(subgroups).containsExactly(group); | |
| } | |
| @Test | |
| public void addMultipleIncludes() throws Exception { | |
| AccountGroup.UUID parent = groupOperations.newGroup().create(); | |
| AccountGroup.UUID group1 = groupOperations.newGroup().create(); | |
| AccountGroup.UUID group2 = groupOperations.newGroup().create(); | |
| gApi.groups().id(parent.get()).addGroups(group1.get(), group2.get()); | |
| ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(parent).get().subgroups(); | |
| assertThat(subgroups).containsExactly(group1, group2); | |
| } | |
| @Test | |
| public void createGroup() throws Exception { | |
| String newGroupName = name("newGroup"); | |
| GroupInfo g = gApi.groups().create(newGroupName).get(); | |
| assertGroupInfo(group(newGroupName), g); | |
| } | |
| @Test | |
| public void createGroupNameIsTrimmed() throws Exception { | |
| String newGroupName = name("newGroup"); | |
| GroupInfo g = gApi.groups().create(" " + newGroupName + " ").get(); | |
| assertGroupInfo(group(newGroupName), g); | |
| } | |
| @Test | |
| public void createDuplicateInternalGroupCaseSensitiveName_Conflict() throws Exception { | |
| String dupGroupName = name("dupGroup"); | |
| gApi.groups().create(dupGroupName); | |
| ResourceConflictException thrown = | |
| assertThrows(ResourceConflictException.class, () -> gApi.groups().create(dupGroupName)); | |
| assertThat(thrown).hasMessageThat().contains("group '" + dupGroupName + "' already exists"); | |
| } | |
| @Test | |
| public void createDuplicateInternalGroupCaseInsensitiveName() throws Exception { | |
| String dupGroupName = name("dupGroupA"); | |
| String dupGroupNameLowerCase = name("dupGroupA").toLowerCase(Locale.US); | |
| gApi.groups().create(dupGroupName); | |
| gApi.groups().create(dupGroupNameLowerCase); | |
| assertThat(gApi.groups().list().getAsMap().keySet()).contains(dupGroupName); | |
| assertThat(gApi.groups().list().getAsMap().keySet()).contains(dupGroupNameLowerCase); | |
| } | |
| @Test | |
| public void createDuplicateSystemGroupCaseSensitiveName_Conflict() throws Exception { | |
| String newGroupName = "Registered Users"; | |
| ResourceConflictException thrown = | |
| assertThrows(ResourceConflictException.class, () -> gApi.groups().create(newGroupName)); | |
| assertThat(thrown).hasMessageThat().contains("group 'Registered Users' already exists"); | |
| } | |
| @Test | |
| public void createDuplicateSystemGroupCaseInsensitiveName_Conflict() throws Exception { | |
| String newGroupName = "registered users"; | |
| ResourceConflictException thrown = | |
| assertThrows(ResourceConflictException.class, () -> gApi.groups().create(newGroupName)); | |
| assertThat(thrown).hasMessageThat().contains("group 'Registered Users' already exists"); | |
| } | |
| @Test | |
| @GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users") | |
| public void createGroupWithConfiguredNameOfSystemGroup_Conflict() throws Exception { | |
| ResourceConflictException thrown = | |
| assertThrows(ResourceConflictException.class, () -> gApi.groups().create("all users")); | |
| assertThat(thrown).hasMessageThat().contains("group 'All Users' already exists"); | |
| } | |
| @Test | |
| @GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users") | |
| public void createGroupWithDefaultNameOfSystemGroup_Conflict() throws Exception { | |
| ResourceConflictException thrown = | |
| assertThrows( | |
| ResourceConflictException.class, () -> gApi.groups().create("anonymous users")); | |
| assertThat(thrown).hasMessageThat().contains("group name 'Anonymous Users' is reserved"); | |
| } | |
| @Test | |
| public void createGroupWithUuid() throws Exception { | |
| AccountGroup.UUID uuid = AccountGroup.UUID.parse("4eb25d1cca562f53b9356117f33840706a36a349"); | |
| GroupInput input = new GroupInput(); | |
| input.uuid = uuid.get(); | |
| input.name = name("new-group"); | |
| GroupInfo info = gApi.groups().create(input).get(); | |
| assertThat(info.name).isEqualTo(input.name); | |
| assertThat(info.id).isEqualTo(input.uuid); | |
| } | |
| @Test | |
| public void createGroupWithExistingUuid_Conflict() throws Exception { | |
| GroupInfo existingGroup = gApi.groups().create(name("new-group")).get(); | |
| GroupInput input = new GroupInput(); | |
| input.uuid = existingGroup.id; | |
| input.name = name("another-new-group"); | |
| ResourceConflictException thrown = | |
| assertThrows(ResourceConflictException.class, () -> gApi.groups().create(input).get()); | |
| assertThat(thrown) | |
| .hasMessageThat() | |
| .isEqualTo(String.format("group with UUID '%s' already exists", input.uuid)); | |
| } | |
| @Test | |
| public void createGroupWithInvalidUuid_BadRequest() throws Exception { | |
| AccountGroup.UUID uuid = AccountGroup.UUID.parse("foo:bar"); | |
| GroupInput input = new GroupInput(); | |
| input.uuid = uuid.get(); | |
| input.name = name("new-group"); | |
| BadRequestException thrown = | |
| assertThrows(BadRequestException.class, () -> gApi.groups().create(input).get()); | |
| assertThat(thrown) | |
| .hasMessageThat() | |
| .isEqualTo(String.format("invalid group UUID '%s'", input.uuid)); | |
| } | |
| @Test | |
| public void createGroupWithProperties() throws Exception { | |
| GroupInput in = new GroupInput(); | |
| in.name = name("newGroup"); | |
| in.description = "Test description"; | |
| in.visibleToAll = true; | |
| in.ownerId = adminGroupUuid().get(); | |
| GroupInfo g = gApi.groups().create(in).detail(); | |
| assertThat(g.description).isEqualTo(in.description); | |
| assertThat(g.options.visibleToAll).isEqualTo(in.visibleToAll); | |
| assertThat(g.ownerId).isEqualTo(in.ownerId); | |
| } | |
| @Test | |
| public void createGroupWithoutCapability_Forbidden() throws Exception { | |
| requestScopeOperations.setApiUser(user.id()); | |
| assertThrows(AuthException.class, () -> gApi.groups().create(name("newGroup"))); | |
| } | |
| // TODO(issue-15508): Migrate timestamp fields in *Info/*Input classes from type Timestamp to | |
| // Instant | |
| @SuppressWarnings("JdkObsolete") | |
| @Test | |
| public void createdOnFieldIsPopulatedForNewGroup() throws Exception { | |
| // NoteDb allows only second precision. | |
| Instant testStartTime = TimeUtil.truncateToSecond(TimeUtil.now()); | |
| String newGroupName = name("newGroup"); | |
| GroupInfo group = gApi.groups().create(newGroupName).get(); | |
| assertThat(group.createdOn.toInstant()).isAtLeast(testStartTime); | |
| } | |
| @Test | |
| public void cachedGroupsForMemberAreUpdatedOnGroupCreation() throws Exception { | |
| Account.Id accountId = accountOperations.newAccount().create(); | |
| // Fill the cache for the observed account. | |
| groupIncludeCache.getGroupsWithMember(accountId); | |
| GroupInput groupInput = new GroupInput(); | |
| groupInput.name = name("Users"); | |
| groupInput.members = ImmutableList.of(String.valueOf(accountId.get())); | |
| GroupInfo group = gApi.groups().create(groupInput).get(); | |
| Collection<AccountGroup.UUID> groups = groupIncludeCache.getGroupsWithMember(accountId); | |
| assertThat(groups).containsExactly(AccountGroup.uuid(group.id)); | |
| } | |
| @Test | |
| public void getGroup() throws Exception { | |
| InternalGroup adminGroup = adminGroup(); | |
| testGetGroup(adminGroup.getGroupUUID().get(), adminGroup); | |
| testGetGroup(adminGroup.getName(), adminGroup); | |
| testGetGroup(adminGroup.getId().get(), adminGroup); | |
| } | |
| private void testGetGroup(Object id, InternalGroup expectedGroup) throws Exception { | |
| GroupInfo group = gApi.groups().id(id.toString()).get(); | |
| assertGroupInfo(expectedGroup, group); | |
| } | |
| @Test | |
| public void getGroupFromMetaId() throws Exception { | |
| AccountGroup.UUID uuid = groupOperations.newGroup().create(); | |
| InternalGroup preUpdateState = groupCache.get(uuid).get(); | |
| gApi.groups().id(uuid.toString()).description("New description"); | |
| InternalGroup postUpdateState = groupCache.get(uuid).get(); | |
| assertThat(postUpdateState).isNotEqualTo(preUpdateState); | |
| assertThat(groupCache.getFromMetaId(uuid, preUpdateState.getRefState())) | |
| .isEqualTo(preUpdateState); | |
| assertThat(groupCache.getFromMetaId(uuid, postUpdateState.getRefState())) | |
| .isEqualTo(postUpdateState); | |
| } | |
| @Test | |
| @GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users") | |
| public void getSystemGroupByConfiguredName() throws Exception { | |
| GroupReference anonymousUsersGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS); | |
| assertThat(anonymousUsersGroup.getName()).isEqualTo("All Users"); | |
| GroupInfo group = gApi.groups().id(anonymousUsersGroup.getUUID().get()).get(); | |
| assertThat(group.name).isEqualTo(anonymousUsersGroup.getName()); | |
| group = gApi.groups().id(anonymousUsersGroup.getName()).get(); | |
| assertThat(group.id).isEqualTo(Url.encode(anonymousUsersGroup.getUUID().get())); | |
| } | |
| @Test | |
| public void getSystemGroupByDefaultName() throws Exception { | |
| GroupReference anonymousUsersGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS); | |
| GroupInfo group = gApi.groups().id("Anonymous Users").get(); | |
| assertThat(group.name).isEqualTo(anonymousUsersGroup.getName()); | |
| assertThat(group.id).isEqualTo(Url.encode(anonymousUsersGroup.getUUID().get())); | |
| } | |
| @Test | |
| @GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users") | |
| public void getSystemGroupByDefaultName_NotFound() throws Exception { | |
| assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id("Anonymous-Users").get()); | |
| } | |
| @Test | |
| public void groupIsCreatedForSpecifiedName() throws Exception { | |
| String name = name("Users"); | |
| gApi.groups().create(name); | |
| assertThat(gApi.groups().id(name).name()).isEqualTo(name); | |
| } | |
| @Test | |
| public void groupCannotBeCreatedWithNameOfAnotherGroup() throws Exception { | |
| String name = name("Users"); | |
| gApi.groups().create(name).get(); | |
| assertThrows(ResourceConflictException.class, () -> gApi.groups().create(name)); | |
| } | |
| @Test | |
| public void groupCanBeRenamed() throws Exception { | |
| String name = name("Name1"); | |
| GroupInfo group = gApi.groups().create(name).get(); | |
| String newName = name("Name2"); | |
| gApi.groups().id(name).name(newName); | |
| assertThat(gApi.groups().id(group.id).name()).isEqualTo(newName); | |
| } | |
| @Test | |
| public void groupCanBeRenamedToItsCurrentName() throws Exception { | |
| String name = name("Users"); | |
| GroupInfo group = gApi.groups().create(name).get(); | |
| gApi.groups().id(group.id).name(name); | |
| assertThat(gApi.groups().id(group.id).name()).isEqualTo(name); | |
| } | |
| @Test | |
| public void groupCannotBeRenamedToNameOfAnotherGroup() throws Exception { | |
| String name1 = name("Name1"); | |
| GroupInfo group1 = gApi.groups().create(name1).get(); | |
| String name2 = name("Name2"); | |
| gApi.groups().create(name2); | |
| assertThrows(ResourceConflictException.class, () -> gApi.groups().id(group1.id).name(name2)); | |
| } | |
| @Test | |
| public void renamedGroupCanBeLookedUpByNewName() throws Exception { | |
| String name = name("Name1"); | |
| GroupInfo group = gApi.groups().create(name).get(); | |
| String newName = name("Name2"); | |
| gApi.groups().id(group.id).name(newName); | |
| GroupInfo foundGroup = gApi.groups().id(newName).get(); | |
| assertThat(foundGroup.id).isEqualTo(group.id); | |
| } | |
| @Test | |
| public void oldNameOfRenamedGroupIsNotAccessibleAnymore() throws Exception { | |
| String name = name("Name1"); | |
| GroupInfo group = gApi.groups().create(name).get(); | |
| String newName = name("Name2"); | |
| gApi.groups().id(group.id).name(newName); | |
| assertGroupDoesNotExist(name); | |
| assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id(name).get()); | |
| } | |
| @Test | |
| public void oldNameOfRenamedGroupIsFreeForUseAgain() throws Exception { | |
| String name = name("Name1"); | |
| GroupInfo group1 = gApi.groups().create(name).get(); | |
| String newName = name("Name2"); | |
| gApi.groups().id(group1.id).name(newName); | |
| GroupInfo group2 = gApi.groups().create(name).get(); | |
| assertThat(group2.id).isNotEqualTo(group1.id); | |
| } | |
| @Test | |
| public void groupDescription() throws Exception { | |
| String name = name("group"); | |
| gApi.groups().create(name); | |
| // get description | |
| assertThat(gApi.groups().id(name).description()).isEmpty(); | |
| // set description | |
| String desc = "New description for the group."; | |
| gApi.groups().id(name).description(desc); | |
| assertThat(gApi.groups().id(name).description()).isEqualTo(desc); | |
| // set description to null | |
| gApi.groups().id(name).description(null); | |
| assertThat(gApi.groups().id(name).description()).isEmpty(); | |
| // set description to empty string | |
| gApi.groups().id(name).description(""); | |
| assertThat(gApi.groups().id(name).description()).isEmpty(); | |
| } | |
| @Test | |
| public void groupOptions() throws Exception { | |
| String name = name("group"); | |
| gApi.groups().create(name); | |
| // get options | |
| assertThat(gApi.groups().id(name).options().visibleToAll).isNull(); | |
| // set options | |
| GroupOptionsInfo options = new GroupOptionsInfo(); | |
| options.visibleToAll = true; | |
| gApi.groups().id(name).options(options); | |
| assertThat(gApi.groups().id(name).options().visibleToAll).isTrue(); | |
| } | |
| @Test | |
| public void groupOwner() throws Exception { | |
| String name = name("group"); | |
| GroupInfo info = gApi.groups().create(name).get(); | |
| String adminUUID = adminGroupUuid().get(); | |
| String registeredUUID = SystemGroupBackend.REGISTERED_USERS.get(); | |
| // get owner | |
| assertThat(Url.decode(gApi.groups().id(name).owner().id)).isEqualTo(info.id); | |
| // set owner by name | |
| gApi.groups().id(name).owner("Registered Users"); | |
| assertThat(Url.decode(gApi.groups().id(name).owner().id)).isEqualTo(registeredUUID); | |
| // set owner by UUID | |
| gApi.groups().id(name).owner(adminUUID); | |
| assertThat(Url.decode(gApi.groups().id(name).owner().id)).isEqualTo(adminUUID); | |
| // set non existing owner | |
| assertThrows( | |
| UnprocessableEntityException.class, | |
| () -> gApi.groups().id(name).owner("Non-Existing Group")); | |
| } | |
| @Test | |
| public void listNonExistingGroupIncludes_NotFound() throws Exception { | |
| assertThrows( | |
| ResourceNotFoundException.class, () -> gApi.groups().id("non-existing").includedGroups()); | |
| } | |
| @Test | |
| public void listEmptyGroupIncludes() throws Exception { | |
| AccountGroup.UUID gx = groupOperations.newGroup().create(); | |
| assertThat(gApi.groups().id(gx.get()).includedGroups()).isEmpty(); | |
| } | |
| @Test | |
| public void includeNonExistingGroup() throws Exception { | |
| AccountGroup.UUID gx = groupOperations.newGroup().create(); | |
| assertThrows( | |
| UnprocessableEntityException.class, | |
| () -> gApi.groups().id(gx.get()).addGroups("non-existing")); | |
| } | |
| @Test | |
| public void listNonEmptyGroupIncludes() throws Exception { | |
| AccountGroup.UUID gz = groupOperations.newGroup().create(); | |
| AccountGroup.UUID gy = groupOperations.newGroup().create(); | |
| AccountGroup.UUID gx = groupOperations.newGroup().subgroups(gy, gz).create(); | |
| List<GroupInfo> includes = gApi.groups().id(gx.get()).includedGroups(); | |
| String gyName = groupOperations.group(gy).get().name(); | |
| String gzName = groupOperations.group(gz).get().name(); | |
| assertIncludes(includes, gyName, gzName); | |
| } | |
| @Test | |
| public void listOneIncludeMember() throws Exception { | |
| AccountGroup.UUID gy = groupOperations.newGroup().create(); | |
| AccountGroup.UUID gx = groupOperations.newGroup().subgroups(gy).create(); | |
| List<GroupInfo> includes = gApi.groups().id(gx.get()).includedGroups(); | |
| String gyName = groupOperations.group(gy).get().name(); | |
| assertIncludes(includes, gyName); | |
| } | |
| @Test | |
| public void listNonExistingGroupMembers_NotFound() throws Exception { | |
| assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id("non-existing").members()); | |
| } | |
| @Test | |
| public void listEmptyGroupMembers() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| assertThat(gApi.groups().id(group.get()).members()).isEmpty(); | |
| } | |
| @Test | |
| public void listNonEmptyGroupMembers() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| String user1 = name("user1"); | |
| accountOperations.newAccount().username(user1).create(); | |
| String user2 = name("user2"); | |
| accountOperations.newAccount().username(user2).create(); | |
| gApi.groups().id(group.get()).addMembers(user1, user2); | |
| assertMembers(gApi.groups().id(group.get()).members(), user1, user2); | |
| } | |
| @Test | |
| public void listOneGroupMember() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| String user = name("user1"); | |
| accountOperations.newAccount().username(user).create(); | |
| gApi.groups().id(group.get()).addMembers(user); | |
| assertMembers(gApi.groups().id(group.get()).members(), user); | |
| } | |
| @Test | |
| public void listGroupMembersRecursively() throws Exception { | |
| AccountGroup.UUID gx = groupOperations.newGroup().create(); | |
| String ux = name("ux"); | |
| accountOperations.newAccount().username(ux).create(); | |
| gApi.groups().id(gx.get()).addMembers(ux); | |
| AccountGroup.UUID gy = groupOperations.newGroup().create(); | |
| String uy = name("uy"); | |
| accountOperations.newAccount().username(uy).create(); | |
| gApi.groups().id(gy.get()).addMembers(uy); | |
| AccountGroup.UUID gz = groupOperations.newGroup().create(); | |
| String uz = name("uz"); | |
| accountOperations.newAccount().username(uz).create(); | |
| gApi.groups().id(gz.get()).addMembers(uz); | |
| gApi.groups().id(gx.get()).addGroups(gy.get()); | |
| gApi.groups().id(gy.get()).addGroups(gz.get()); | |
| assertMembers(gApi.groups().id(gx.get()).members(), ux); | |
| assertMembers(gApi.groups().id(gx.get()).members(true), ux, uy, uz); | |
| } | |
| @Test | |
| public void usersSeeTheirDirectMembershipWhenListingMembersRecursively() throws Exception { | |
| AccountGroup.UUID group = groupOperations.newGroup().create(); | |
| gApi.groups().id(group.get()).addMembers(user.username()); | |
| requestScopeOperations.setApiUser(user.id()); | |
| assertMembers(gApi.groups().id(group.get()).members(true), user.fullName()); | |
| } | |
| @Test | |
| public void usersDoNotSeeTheirIndirectMembershipWhenListingMembersRecursively() throws Exception { | |
| AccountGroup.UUID group1 = groupOperations.newGroup().ownerGroupUuid(adminGroupUuid()).create(); | |
| AccountGroup.UUID group2 = groupOperations.newGroup().ownerGroupUuid(adminGroupUuid()).create(); | |
| gApi.groups().id(group1.get()).addGroups(group2.get()); | |
| gApi.groups().id(group2.get()).addMembers(user.username()); | |
| requestScopeOperations.setApiUser(user.id()); | |
| List<AccountInfo> listedMembers = gApi.groups().id(group1.get()).members(true); | |
| assertMembers(listedMembers); | |
| } | |
| @Test | |
| public void adminsSeeTheirIndirectMembershipWhenListingMembersRecursively() throws Exception { | |
| AccountGroup.UUID ownerGroup = groupOperations.newGroup().create(); | |
| AccountGroup.UUID group1 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create(); | |
| AccountGroup.UUID group2 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create(); | |
| gApi.groups().id(group1.get()).addGroups(group2.get()); | |
| gApi.groups().id(group2.get()).addMembers(admin.username()); | |
| List<AccountInfo> listedMembers = gApi.groups().id(group1.get()).members(true); | |
| assertMembers(listedMembers, admin.fullName()); | |
| } | |
| @Test | |
| public void ownersSeeTheirIndirectMembershipWhenListingMembersRecursively() throws Exception { | |
| AccountGroup.UUID ownerGroup = groupOperations.newGroup().create(); | |
| AccountGroup.UUID group1 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create(); | |
| AccountGroup.UUID group2 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create(); | |
| gApi.groups().id(group1.get()).addGroups(group2.get()); | |
| gApi.groups().id(ownerGroup.get()).addMembers(user.username()); | |
| gApi.groups().id(group2.get()).addMembers(user.username()); | |
| requestScopeOperations.setApiUser(user.id()); | |
| List<AccountInfo> listedMembers = gApi.groups().id(group1.get()).members(true); | |
| assertMembers(listedMembers, user.fullName()); | |
| } | |
| @Test | |
| public void defaultGroupsCreated() throws Exception { | |
| Iterable<String> names = gApi.groups().list().getAsMap().keySet(); | |
| assertThat(names) | |
| .containsAtLeast("Administrators", ServiceUserClassifier.SERVICE_USERS) | |
| .inOrder(); | |
| } | |
| @Test | |
| public void listAllGroups() throws Exception { | |
| List<String> expectedGroups = | |
| groups.getAllGroupReferences().map(GroupReference::getName).sorted().collect(toList()); | |
| assertThat(expectedGroups.size()).isAtLeast(2); | |
| assertThatMap(gApi.groups().list().getAsMap()) | |
| .keys() | |
| .containsExactlyElementsIn(expectedGroups) | |
| .inOrder(); | |
| } | |
| @Test | |
| public void getGroupsByOwner() throws Exception { | |
| AccountGroup.UUID parent = groupOperations.newGroup().ownerGroupUuid(adminGroupUuid()).create(); | |
| List<AccountGroup.UUID> children = | |
| Arrays.asList( | |
| groupOperations.newGroup().ownerGroupUuid(parent).create(), | |
| groupOperations.newGroup().ownerGroupUuid(parent).create()); | |
| // By UUID | |
| List<GroupInfo> owned = gApi.groups().list().withOwnedBy(parent.get()).get(); | |
| assertThat(owned.stream().map(g -> AccountGroup.uuid(g.id)).collect(toList())) | |
| .containsExactlyElementsIn(children); | |
| // By name | |
| String parentName = groupOperations.group(parent).get().name(); | |
| owned = gApi.groups().list().withOwnedBy(parentName).get(); | |
| assertThat(owned.stream().map(g -> AccountGroup.uuid(g.id)).collect(toList())) | |
| .containsExactlyElementsIn(children); | |
| // By group that does not own any others | |
| owned = gApi.groups().list().withOwnedBy(owned.get(0).id).get(); | |
| assertThat(owned).isEmpty(); | |
| // By non-existing group | |
| UnprocessableEntityException thrown = | |
| assertThrows( | |
| UnprocessableEntityException.class, | |
| () -> gApi.groups().list().withOwnedBy("does-not-exist").get()); | |
| assertThat(thrown).hasMessageThat().contains("Group Not Found: does-not-exist"); | |
| } | |
| @Test | |
| public void onlyVisibleGroupsReturned() throws Exception { | |
| String newGroupName = name("newGroup"); | |
| GroupInput in = new GroupInput(); | |
| in.name = newGroupName; | |
| in.description = "a hidden group"; | |
| in.visibleToAll = false; | |
| in.ownerId = adminGroupUuid().get(); | |
| gApi.groups().create(in); | |
| requestScopeOperations.setApiUser(user.id()); | |
| assertThatMap(gApi.groups().list().getAsMap()).keys().doesNotContain(newGroupName); | |
| requestScopeOperations.setApiUser(admin.id()); | |
| gApi.groups().id(newGroupName).addMembers(user.username()); | |
| requestScopeOperations.setApiUser(user.id()); | |
| assertThatMap(gApi.groups().list().getAsMap()).keys().contains(newGroupName); | |
| } | |
| @Test | |
| public void suggestGroup() throws Exception { | |
| Map<String, GroupInfo> groups = gApi.groups().list().withSuggest("adm").getAsMap(); | |
| assertThatMap(groups).keys().containsExactly("Administrators"); | |
| assertBadRequest(gApi.groups().list().withSuggest("adm").withSubstring("foo")); | |
| assertBadRequest(gApi.groups().list().withSuggest("adm").withRegex("foo.*")); | |
| assertBadRequest(gApi.groups().list().withSuggest("adm").withUser("user")); | |
| assertBadRequest(gApi.groups().list().withSuggest("adm").withOwned(true)); | |
| assertBadRequest(gApi.groups().list().withSuggest("adm").withVisibleToAll(true)); | |
| assertBadRequest(gApi.groups().list().withSuggest("adm").withStart(1)); | |
| } | |
| @Test | |
| public void withSubstring() throws Exception { | |
| String group = name("Abcdefghijklmnop"); | |
| gApi.groups().create(group); | |
| // Choose a substring which isn't part of any group or test method within this class. | |
| String substring = "efghijk"; | |
| Map<String, GroupInfo> groups = gApi.groups().list().withSubstring(substring).getAsMap(); | |
| assertThatMap(groups).keys().containsExactly(group); | |
| groups = gApi.groups().list().withSubstring("abcdefghi").getAsMap(); | |
| assertThatMap(groups).keys().containsExactly(group); | |
| String otherGroup = name("Abcdefghijklmnop2"); | |
| gApi.groups().create(otherGroup); | |
| groups = gApi.groups().list().withSubstring(substring).getAsMap(); | |
| assertThatMap(groups).keys().containsExactly(group, otherGroup); | |
| groups = gApi.groups().list().withSubstring("non-existing-substring").getAsMap(); | |
| assertThat(groups).isEmpty(); | |
| } | |
| @Test | |
| public void withRegex() throws Exception { | |
| Map<String, GroupInfo> groups = gApi.groups().list().withRegex("Admin.*").getAsMap(); | |
| assertThatMap(groups).keys().containsExactly("Administrators"); | |
| groups = gApi.groups().list().withRegex("admin.*").getAsMap(); | |
| assertThat(groups).isEmpty(); | |
| groups = gApi.groups().list().withRegex(".*istrators").getAsMap(); | |
| assertThatMap(groups).keys().containsExactly("Administrators"); | |
| assertBadRequest(gApi.groups().list().withRegex(".*istrators").withSubstring("s")); | |
| } | |
| @Test | |
| public void allGroupInfoFieldsSetCorrectly() throws Exception { | |
| InternalGroup adminGroup = adminGroup(); | |
| Map<String, GroupInfo> groups = gApi.groups().list().addGroup(adminGroup.getName()).getAsMap(); | |
| assertThatMap(groups).keys().containsExactly("Administrators"); | |
| assertGroupInfo(adminGroup, Iterables.getOnlyElement(groups.values())); | |
| } | |
| @Test | |
| public void getAuditLog() throws Exception { | |
| GroupApi g = gApi.groups().create(name("group")); | |
| List<? extends GroupAuditEventInfo> auditEvents = g.auditLog(); | |
| assertThat(auditEvents).hasSize(1); | |
| assertMemberAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.ADD_USER, admin.id(), admin.id()); | |
| g.addMembers(user.username()); | |
| auditEvents = g.auditLog(); | |
| assertThat(auditEvents).hasSize(2); | |
| assertMemberAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.ADD_USER, admin.id(), user.id()); | |
| g.removeMembers(user.username()); | |
| auditEvents = g.auditLog(); | |
| assertThat(auditEvents).hasSize(3); | |
| assertMemberAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.REMOVE_USER, admin.id(), user.id()); | |
| String otherGroup = name("otherGroup"); | |
| gApi.groups().create(otherGroup); | |
| g.addGroups(otherGroup); | |
| auditEvents = g.auditLog(); | |
| assertThat(auditEvents).hasSize(4); | |
| assertSubgroupAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.ADD_GROUP, admin.id(), otherGroup); | |
| g.removeGroups(otherGroup); | |
| auditEvents = g.auditLog(); | |
| assertThat(auditEvents).hasSize(5); | |
| assertSubgroupAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.REMOVE_GROUP, admin.id(), otherGroup); | |
| // Add a removed member back again. | |
| g.addMembers(user.username()); | |
| auditEvents = g.auditLog(); | |
| assertThat(auditEvents).hasSize(6); | |
| assertMemberAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.ADD_USER, admin.id(), user.id()); | |
| // Add a removed group back again. | |
| g.addGroups(otherGroup); | |
| auditEvents = g.auditLog(); | |
| assertThat(auditEvents).hasSize(7); | |
| assertSubgroupAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.ADD_GROUP, admin.id(), otherGroup); | |
| Timestamp lastDate = null; | |
| for (GroupAuditEventInfo auditEvent : auditEvents) { | |
| if (lastDate != null) { | |
| assertThat(lastDate).isAtLeast(auditEvent.date); | |
| } | |
| lastDate = auditEvent.date; | |
| } | |
| } | |
| /** | |
| * {@code @Sandboxed} is used by this test because it deletes a group reference which introduces | |
| * an inconsistency for the group storage. Once group deletion is supported, this test should be | |
| * updated to use the API instead. | |
| */ | |
| @Test | |
| @Sandboxed | |
| @IgnoreGroupInconsistencies | |
| public void getAuditLogAfterDeletingASubgroup() throws Exception { | |
| GroupInfo parentGroup = gApi.groups().create(name("parent-group")).get(); | |
| // Creates a subgroup and adds it to "parent-group" as a subgroup. | |
| GroupInfo subgroup = gApi.groups().create(name("sub-group")).get(); | |
| gApi.groups().id(parentGroup.id).addGroups(subgroup.id); | |
| // Deletes the subgroup. | |
| deleteGroupRef(subgroup.id); | |
| List<? extends GroupAuditEventInfo> auditEvents = gApi.groups().id(parentGroup.id).auditLog(); | |
| assertThat(auditEvents).hasSize(2); | |
| // Verify the unavailable subgroup's name is null. | |
| assertSubgroupAuditEvent( | |
| auditEvents.get(0), GroupAuditEventInfo.Type.ADD_GROUP, admin.id(), null); | |
| } | |
| private void deleteGroupRef(String groupId) throws Exception { | |
| AccountGroup.UUID uuid = AccountGroup.uuid(groupId); | |
| try (Repository repo = repoManager.openRepository(allUsers)) { | |
| RefUpdate ru = repo.updateRef(RefNames.refsGroups(uuid)); | |
| ru.setForceUpdate(true); | |
| ru.setNewObjectId(ObjectId.zeroId()); | |
| testRefAction(() -> assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED)); | |
| } | |
| // Reindex the group. | |
| gApi.groups().id(uuid.get()).index(); | |
| // Verify "sub-group" has been deleted. | |
| assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id(uuid.get()).get()); | |
| } | |
| // reindex is tested by {@link AbstractQueryGroupsTest#reindex} | |
| @Test | |
| public void reindexPermissions() throws Exception { | |
| TestAccount groupOwner = accountCreator.user2(); | |
| GroupInput in = new GroupInput(); | |
| in.name = name("group"); | |
| in.members = Stream.of(groupOwner).map(u -> u.id().toString()).collect(toList()); | |
| in.visibleToAll = true; | |
| GroupInfo group = gApi.groups().create(in).get(); | |
| // admin can reindex any group | |
| requestScopeOperations.setApiUser(admin.id()); | |
| gApi.groups().id(group.id).index(); | |
| // group owner can reindex own group (group is owned by itself) | |
| requestScopeOperations.setApiUser(groupOwner.id()); | |
| gApi.groups().id(group.id).index(); | |
| // user cannot reindex any group | |
| requestScopeOperations.setApiUser(user.id()); | |
| AuthException thrown = | |
| assertThrows(AuthException.class, () -> gApi.groups().id(group.id).index()); | |
| assertThat(thrown).hasMessageThat().contains("not allowed to index group"); | |
| } | |
| @Test | |
| public void pushToGroupBranchIsRejectedForAllUsersRepo() throws Exception { | |
| assertPushToGroupBranch( | |
| allUsers, RefNames.refsGroups(adminGroupUuid()), "group update not allowed"); | |
| } | |
| @Test | |
| public void pushToDeletedGroupBranchIsRejectedForAllUsersRepo() throws Exception { | |
| // refs/deleted-groups is only visible with ACCESS_DATABASE | |
| projectOperations | |
| .allProjectsForUpdate() | |
| .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) | |
| .update(); | |
| String groupRef = | |
| RefNames.refsDeletedGroups(AccountGroup.uuid(gApi.groups().create(name("foo")).get().id)); | |
| createBranch(allUsers, groupRef); | |
| assertPushToGroupBranch(allUsers, groupRef, "group update not allowed"); | |
| } | |
| @Test | |
| public void pushToGroupNamesBranchIsRejectedForAllUsersRepo() throws Exception { | |
| // refs/meta/group-names isn't usually available for fetch, so grant ACCESS_DATABASE | |
| projectOperations | |
| .allProjectsForUpdate() | |
| .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) | |
| .update(); | |
| assertPushToGroupBranch(allUsers, RefNames.REFS_GROUPNAMES, "group update not allowed"); | |
| } | |
| @Test | |
| public void pushToGroupsBranchForNonAllUsersRepo() throws Exception { | |
| assertCreateGroupBranch(project); | |
| String groupRef = | |
| RefNames.refsGroups(AccountGroup.uuid(gApi.groups().create(name("foo")).get().id)); | |
| createBranch(project, groupRef); | |
| assertPushToGroupBranch(project, groupRef, null); | |
| } | |
| @Test | |
| public void pushToDeletedGroupsBranchForNonAllUsersRepo() throws Exception { | |
| assertCreateGroupBranch(project); | |
| String groupRef = | |
| RefNames.refsDeletedGroups(AccountGroup.uuid(gApi.groups().create(name("foo")).get().id)); | |
| createBranch(project, groupRef); | |
| assertPushToGroupBranch(project, groupRef, null); | |
| } | |
| @Test | |
| public void pushToGroupNamesBranchForNonAllUsersRepo() throws Exception { | |
| createBranch(project, RefNames.REFS_GROUPNAMES); | |
| assertPushToGroupBranch(project, RefNames.REFS_GROUPNAMES, null); | |
| } | |
| private void assertPushToGroupBranch( | |
| Project.NameKey project, String groupRefName, String expectedErrorOnUpdate) throws Exception { | |
| projectOperations | |
| .project(project) | |
| .forUpdate() | |
| .add(allow(Permission.CREATE).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS)) | |
| .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS)) | |
| .add( | |
| allow(Permission.CREATE) | |
| .ref(RefNames.REFS_DELETED_GROUPS + "*") | |
| .group(REGISTERED_USERS)) | |
| .add(allow(Permission.PUSH).ref(RefNames.REFS_DELETED_GROUPS + "*").group(REGISTERED_USERS)) | |
| .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPNAMES).group(REGISTERED_USERS)) | |
| .update(); | |
| TestRepository<InMemoryRepository> repo = cloneProject(project); | |
| // update existing branch | |
| fetch(repo, groupRefName + ":groupRef"); | |
| repo.reset("groupRef"); | |
| PushOneCommit.Result r = | |
| pushFactory | |
| .create(admin.newIdent(), repo, "Update group", "arbitraryFile.txt", "some content") | |
| .to(groupRefName); | |
| if (expectedErrorOnUpdate != null) { | |
| r.assertErrorStatus(expectedErrorOnUpdate); | |
| } else { | |
| r.assertOkStatus(); | |
| } | |
| } | |
| private void assertCreateGroupBranch(Project.NameKey project) throws Exception { | |
| projectOperations | |
| .project(project) | |
| .forUpdate() | |
| .add(allow(Permission.CREATE).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS)) | |
| .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS)) | |
| .update(); | |
| TestRepository<InMemoryRepository> repo = cloneProject(project); | |
| PushOneCommit.Result r = | |
| pushFactory | |
| .create(admin.newIdent(), repo, "Update group", "arbitraryFile.txt", "some content") | |
| .setParents(ImmutableList.of()) | |
| .to(RefNames.REFS_GROUPS + name("bar")); | |
| r.assertOkStatus(); | |
| } | |
| @Test | |
| public void pushToGroupBranchForReviewForAllUsersRepoIsRejectedOnSubmitForGroupFiles() | |
| throws Throwable { | |
| String error = "update to group files (group.config, members, subgroups) not allowed"; | |
| pushToGroupBranchForReviewAndSubmit( | |
| allUsers, RefNames.refsGroups(adminGroupUuid()), "group.config", error); | |
| pushToGroupBranchForReviewAndSubmit( | |
| allUsers, RefNames.refsGroups(adminGroupUuid()), "members", error); | |
| pushToGroupBranchForReviewAndSubmit( | |
| allUsers, RefNames.refsGroups(adminGroupUuid()), "subgroups", error); | |
| pushToGroupBranchForReviewAndSubmit( | |
| allUsers, RefNames.refsGroups(adminGroupUuid()), "destinations/myreviews", null); | |
| } | |
| @Test | |
| public void pushToGroupBranchForReviewForNonAllUsersRepoAndSubmit() throws Throwable { | |
| String groupRef = RefNames.refsGroups(adminGroupUuid()); | |
| createBranch(project, groupRef); | |
| pushToGroupBranchForReviewAndSubmit(project, groupRef, "group.config", null); | |
| } | |
| @Test | |
| public void pushCustomInheritanceForAllUsersFails() throws Exception { | |
| TestRepository<InMemoryRepository> repo = cloneProject(allUsers); | |
| GitUtil.fetch(repo, RefNames.REFS_CONFIG + ":" + RefNames.REFS_CONFIG); | |
| repo.reset(RefNames.REFS_CONFIG); | |
| String config = | |
| gApi.projects() | |
| .name(allUsers.get()) | |
| .branch(RefNames.REFS_CONFIG) | |
| .file("project.config") | |
| .asString(); | |
| Config cfg = new Config(); | |
| cfg.fromText(config); | |
| cfg.setString("access", null, "inheritFrom", project.get()); | |
| config = cfg.toText(); | |
| PushOneCommit.Result r = | |
| pushFactory | |
| .create(admin.newIdent(), repo, "Subject", "project.config", config) | |
| .to(RefNames.REFS_CONFIG); | |
| r.assertErrorStatus("invalid project configuration"); | |
| r.assertMessage("All-Users must inherit from All-Projects"); | |
| } | |
| @Test | |
| public void cannotCreateGroupBranch() throws Exception { | |
| testCannotCreateGroupBranch( | |
| RefNames.REFS_GROUPS + "*", RefNames.refsGroups(AccountGroup.uuid(name("foo")))); | |
| } | |
| @Test | |
| public void cannotCreateDeletedGroupBranch() throws Exception { | |
| testCannotCreateGroupBranch( | |
| RefNames.REFS_DELETED_GROUPS + "*", | |
| RefNames.refsDeletedGroups(AccountGroup.uuid(name("foo")))); | |
| } | |
| @Test | |
| @IgnoreGroupInconsistencies | |
| public void cannotCreateGroupNamesBranch() throws Exception { | |
| // Use ProjectResetter to restore the group names ref | |
| try (ProjectResetter resetter = | |
| projectResetter | |
| .builder() | |
| .build(new ProjectResetter.Config().reset(allUsers, RefNames.REFS_GROUPNAMES))) { | |
| // Manually delete group names ref | |
| try (Repository repo = repoManager.openRepository(allUsers); | |
| RevWalk rw = new RevWalk(repo)) { | |
| RevCommit commit = rw.parseCommit(repo.exactRef(RefNames.REFS_GROUPNAMES).getObjectId()); | |
| RefUpdate updateRef = repo.updateRef(RefNames.REFS_GROUPNAMES); | |
| updateRef.setExpectedOldObjectId(commit.toObjectId()); | |
| updateRef.setNewObjectId(ObjectId.zeroId()); | |
| updateRef.setForceUpdate(true); | |
| testRefAction(() -> assertThat(updateRef.delete()).isEqualTo(RefUpdate.Result.FORCED)); | |
| } | |
| // refs/meta/group-names is only visible with ACCESS_DATABASE | |
| projectOperations | |
| .allProjectsForUpdate() | |
| .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) | |
| .update(); | |
| testCannotCreateGroupBranch(RefNames.REFS_GROUPNAMES, RefNames.REFS_GROUPNAMES); | |
| } | |
| } | |
| private void testCannotCreateGroupBranch(String refPattern, String groupRef) throws Exception { | |
| projectOperations | |
| .project(allUsers) | |
| .forUpdate() | |
| .add(allow(Permission.CREATE).ref(refPattern).group(adminGroupUuid())) | |
| .add(allow(Permission.PUSH).ref(refPattern).group(adminGroupUuid())) | |
| .update(); | |
| TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); | |
| PushOneCommit.Result r = pushFactory.create(admin.newIdent(), allUsersRepo).to(groupRef); | |
| r.assertErrorStatus(); | |
| assertThat(r.getMessage()).contains("Not allowed to create group branch."); | |
| try (Repository repo = repoManager.openRepository(allUsers)) { | |
| assertThat(repo.exactRef(groupRef)).isNull(); | |
| } | |
| } | |
| @Test | |
| public void cannotDeleteGroupBranch() throws Exception { | |
| testCannotDeleteGroupBranch(RefNames.REFS_GROUPS + "*", RefNames.refsGroups(adminGroupUuid())); | |
| } | |
| @Test | |
| public void cannotDeleteDeletedGroupBranch() throws Exception { | |
| // refs/deleted-groups is only visible with ACCESS_DATABASE | |
| projectOperations | |
| .allProjectsForUpdate() | |
| .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) | |
| .update(); | |
| String groupRef = RefNames.refsDeletedGroups(AccountGroup.uuid(name("foo"))); | |
| createBranch(allUsers, groupRef); | |
| testCannotDeleteGroupBranch(RefNames.REFS_DELETED_GROUPS + "*", groupRef); | |
| } | |
| @Test | |
| public void cannotDeleteGroupNamesBranch() throws Exception { | |
| // refs/meta/group-names is only visible with ACCESS_DATABASE | |
| projectOperations | |
| .allProjectsForUpdate() | |
| .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) | |
| .update(); | |
| testCannotDeleteGroupBranch(RefNames.REFS_GROUPNAMES, RefNames.REFS_GROUPNAMES); | |
| } | |
| private void testCannotDeleteGroupBranch(String refPattern, String groupRef) throws Exception { | |
| projectOperations | |
| .project(allUsers) | |
| .forUpdate() | |
| .add(allow(Permission.DELETE).ref(refPattern).group(REGISTERED_USERS).force(true)) | |
| .update(); | |
| TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); | |
| PushResult r = deleteRef(allUsersRepo, groupRef); | |
| RemoteRefUpdate refUpdate = r.getRemoteUpdate(groupRef); | |
| assertThat(refUpdate.getStatus()).isEqualTo(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); | |
| assertThat(refUpdate.getMessage()).contains("Not allowed to delete group branch."); | |
| try (Repository repo = repoManager.openRepository(allUsers)) { | |
| assertThat(repo.exactRef(groupRef)).isNotNull(); | |
| } | |
| } | |
| @Test | |
| public void defaultPermissionsOnGroupBranches() throws Exception { | |
| assertPermissions( | |
| allUsers, groupRef(REGISTERED_USERS), RefNames.REFS_GROUPS + "*", true, Permission.READ); | |
| } | |
| @Test | |
| @IgnoreGroupInconsistencies | |
| public void stalenessChecker() throws Exception { | |
| // Newly created group is not stale | |
| GroupInfo groupInfo = gApi.groups().create(name("foo")).get(); | |
| AccountGroup.UUID groupUuid = AccountGroup.uuid(groupInfo.id); | |
| assertThat(stalenessChecker.check(groupUuid).isStale()).isFalse(); | |
| // Manual update makes index document stale | |
| String groupRef = RefNames.refsGroups(groupUuid); | |
| try (Repository repo = repoManager.openRepository(allUsers); | |
| RevWalk rw = new RevWalk(repo)) { | |
| RevCommit commit = rw.parseCommit(repo.exactRef(groupRef).getObjectId()); | |
| ObjectId emptyCommit = createCommit(repo, commit.getFullMessage(), commit.getTree()); | |
| RefUpdate updateRef = repo.updateRef(groupRef); | |
| updateRef.setExpectedOldObjectId(commit.toObjectId()); | |
| updateRef.setNewObjectId(emptyCommit); | |
| testRefAction(() -> assertThat(updateRef.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED)); | |
| } | |
| assertStaleGroupAndReindex(groupUuid); | |
| // Manually delete group | |
| try (Repository repo = repoManager.openRepository(allUsers); | |
| RevWalk rw = new RevWalk(repo)) { | |
| RevCommit commit = rw.parseCommit(repo.exactRef(groupRef).getObjectId()); | |
| RefUpdate updateRef = repo.updateRef(groupRef); | |
| updateRef.setExpectedOldObjectId(commit.toObjectId()); | |
| updateRef.setNewObjectId(ObjectId.zeroId()); | |
| updateRef.setForceUpdate(true); | |
| testRefAction(() -> assertThat(updateRef.delete()).isEqualTo(RefUpdate.Result.FORCED)); | |
| } | |
| assertStaleGroupAndReindex(groupUuid); | |
| } | |
| @Test | |
| @Sandboxed | |
| public void groupsOfUserCanBeListedInSlaveMode() throws Exception { | |
| GroupInput groupInput = new GroupInput(); | |
| groupInput.name = name("contributors"); | |
| groupInput.members = ImmutableList.of(user.username()); | |
| gApi.groups().create(groupInput).get(); | |
| restartAsSlave(); | |
| requestScopeOperations.setApiUser(user.id()); | |
| List<GroupInfo> groups = gApi.groups().list().withUser(user.username()).get(); | |
| ImmutableList<String> groupNames = | |
| groups.stream().map(group -> group.name).collect(toImmutableList()); | |
| assertThat(groupNames).contains(groupInput.name); | |
| } | |
| @Test | |
| @Sandboxed | |
| @GerritConfig(name = "index.scheduledIndexer.enabled", value = "false") | |
| @GerritConfig(name = "index.autoReindexIfStale", value = "false") | |
| @IgnoreGroupInconsistencies | |
| public void reindexGroupsInSlaveMode() throws Exception { | |
| List<AccountGroup.UUID> expectedGroups = | |
| groups.getAllGroupReferences().map(GroupReference::getUUID).collect(toList()); | |
| assertThat(expectedGroups.size()).isAtLeast(2); | |
| // Restart the server as slave, on startup of the slave all groups are indexed. | |
| restartAsSlave(); | |
| GroupIndexedCounter groupIndexedCounter = new GroupIndexedCounter(); | |
| try (Registration registration = extensionRegistry.newRegistration().add(groupIndexedCounter)) { | |
| // Running the reindexer right after startup should not need to reindex any group since | |
| // reindexing was already done on startup. | |
| slaveGroupIndexer.run(); | |
| groupIndexedCounter.assertNoReindex(); | |
| // Create a group without updating the cache or index, | |
| // then run the reindexer -> only the new group is reindexed. | |
| String groupName = "foo"; | |
| AccountGroup.UUID groupUuid = AccountGroup.uuid(groupName + "-UUID"); | |
| testRefAction( | |
| () -> | |
| groupsUpdate.createGroupInNoteDb( | |
| InternalGroupCreation.builder() | |
| .setGroupUUID(groupUuid) | |
| .setNameKey(AccountGroup.nameKey(groupName)) | |
| .setId(AccountGroup.id(seq.nextGroupId())) | |
| .build(), | |
| GroupDelta.builder().build())); | |
| slaveGroupIndexer.run(); | |
| groupIndexedCounter.assertReindexOf(groupUuid); | |
| // Update a group without updating the cache or index, | |
| // then run the reindexer -> only the updated group is reindexed. | |
| groupsUpdate.updateGroupInNoteDb( | |
| groupUuid, GroupDelta.builder().setDescription("bar").build()); | |
| slaveGroupIndexer.run(); | |
| groupIndexedCounter.assertReindexOf(groupUuid); | |
| // Delete a group without updating the cache or index, | |
| // then run the reindexer -> only the deleted group is reindexed. | |
| try (Repository repo = repoManager.openRepository(allUsers)) { | |
| RefUpdate u = repo.updateRef(RefNames.refsGroups(groupUuid)); | |
| u.setForceUpdate(true); | |
| testRefAction(() -> assertThat(u.delete()).isEqualTo(RefUpdate.Result.FORCED)); | |
| } | |
| slaveGroupIndexer.run(); | |
| groupIndexedCounter.assertReindexOf(groupUuid); | |
| } | |
| } | |
| @Test | |
| @Sandboxed | |
| @GerritConfig(name = "index.scheduledIndexer.runOnStartup", value = "false") | |
| @GerritConfig(name = "index.scheduledIndexer.enabled", value = "false") | |
| @GerritConfig(name = "index.autoReindexIfStale", value = "false") | |
| @IgnoreGroupInconsistencies | |
| public void disabledReindexGroupsOnStartupSlaveMode() throws Exception { | |
| List<AccountGroup.UUID> expectedGroups = | |
| groups.getAllGroupReferences().map(GroupReference::getUUID).collect(toList()); | |
| assertThat(expectedGroups.size()).isAtLeast(2); | |
| restartAsSlave(); | |
| GroupIndexedCounter groupIndexedCounter = new GroupIndexedCounter(); | |
| try (Registration registration = extensionRegistry.newRegistration().add(groupIndexedCounter)) { | |
| // No group indexing happened on startup. All groups should be reindexed now. | |
| slaveGroupIndexer.run(); | |
| groupIndexedCounter.assertReindexOf(expectedGroups); | |
| } | |
| } | |
| private static Correspondence<AccountInfo, String> getAccountToUsernameCorrespondence() { | |
| return NullAwareCorrespondence.transforming( | |
| accountInfo -> accountInfo.username, "has username"); | |
| } | |
| private void assertStaleGroupAndReindex(AccountGroup.UUID groupUuid) throws IOException { | |
| // Evict group from cache to be sure that we use the index state for staleness checks. | |
| groupCache.evict(groupUuid); | |
| assertThat(stalenessChecker.check(groupUuid).isStale()).isTrue(); | |
| // Reindex fixes staleness | |
| groupIndexer.index(groupUuid); | |
| assertThat(stalenessChecker.check(groupUuid).isStale()).isFalse(); | |
| } | |
| private void pushToGroupBranchForReviewAndSubmit( | |
| Project.NameKey project, String groupRef, String fileName, String expectedError) | |
| throws Throwable { | |
| projectOperations | |
| .project(project) | |
| .forUpdate() | |
| .add( | |
| allowLabel(LabelId.CODE_REVIEW) | |
| .ref(RefNames.REFS_GROUPS + "*") | |
| .group(REGISTERED_USERS) | |
| .range(-2, 2)) | |
| .add(allow(Permission.SUBMIT).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS)) | |
| .update(); | |
| TestRepository<InMemoryRepository> repo = cloneProject(project); | |
| fetch(repo, groupRef + ":groupRef"); | |
| repo.reset("groupRef"); | |
| PushOneCommit.Result r = | |
| pushFactory | |
| .create(admin.newIdent(), repo, "Update group config", fileName, "some content") | |
| .to(MagicBranch.NEW_CHANGE + groupRef); | |
| r.assertOkStatus(); | |
| assertThat(r.getChange().change().getDest().branch()).isEqualTo(groupRef); | |
| gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve()); | |
| ThrowingRunnable submit = () -> gApi.changes().id(r.getChangeId()).current().submit(); | |
| if (expectedError != null) { | |
| Throwable thrown = assertThrows(ResourceConflictException.class, submit); | |
| assertThat(thrown).hasMessageThat().contains(expectedError); | |
| } else { | |
| submit.run(); | |
| } | |
| } | |
| private void createBranch(Project.NameKey project, String ref) throws IOException { | |
| try (Repository r = repoManager.openRepository(project); | |
| ObjectInserter oi = r.newObjectInserter(); | |
| RevWalk rw = new RevWalk(r)) { | |
| ObjectId emptyCommit = createCommit(r, "Test change"); | |
| RefUpdate updateRef = r.updateRef(ref); | |
| updateRef.setExpectedOldObjectId(ObjectId.zeroId()); | |
| updateRef.setNewObjectId(emptyCommit); | |
| testRefAction(() -> assertThat(updateRef.update(rw)).isEqualTo(RefUpdate.Result.NEW)); | |
| } | |
| } | |
| private ObjectId createCommit(Repository repo, String commitMessage) throws IOException { | |
| return createCommit(repo, commitMessage, null); | |
| } | |
| private ObjectId createCommit(Repository repo, String commitMessage, @Nullable ObjectId treeId) | |
| throws IOException { | |
| try (ObjectInserter oi = repo.newObjectInserter()) { | |
| if (treeId == null) { | |
| treeId = oi.insert(Constants.OBJ_TREE, new byte[] {}); | |
| } | |
| PersonIdent ident = new PersonIdent(serverIdent.get(), TimeUtil.now()); | |
| CommitBuilder cb = new CommitBuilder(); | |
| cb.setTreeId(treeId); | |
| cb.setCommitter(ident); | |
| cb.setAuthor(ident); | |
| cb.setMessage(commitMessage); | |
| ObjectId commit = oi.insert(cb); | |
| oi.flush(); | |
| return commit; | |
| } | |
| } | |
| private void assertMemberAuditEvent( | |
| GroupAuditEventInfo info, | |
| GroupAuditEventInfo.Type expectedType, | |
| Account.Id expectedUser, | |
| Account.Id expectedMember) { | |
| assertThat(info.user._accountId).isEqualTo(expectedUser.get()); | |
| assertThat(info.type).isEqualTo(expectedType); | |
| assertThat(info).isInstanceOf(UserMemberAuditEventInfo.class); | |
| assertThat(((UserMemberAuditEventInfo) info).member._accountId).isEqualTo(expectedMember.get()); | |
| } | |
| private void assertSubgroupAuditEvent( | |
| GroupAuditEventInfo info, | |
| GroupAuditEventInfo.Type expectedType, | |
| Account.Id expectedUser, | |
| String expectedMemberGroupName) { | |
| assertThat(info.user._accountId).isEqualTo(expectedUser.get()); | |
| assertThat(info.type).isEqualTo(expectedType); | |
| assertThat(info).isInstanceOf(GroupMemberAuditEventInfo.class); | |
| assertThat(((GroupMemberAuditEventInfo) info).member.name).isEqualTo(expectedMemberGroupName); | |
| } | |
| private void assertMembers(String group, TestAccount... expectedMembers) throws Exception { | |
| assertMembers( | |
| gApi.groups().id(group).members(), | |
| TestAccount.names(expectedMembers).toArray(new String[0])); | |
| assertAccountInfos(Arrays.asList(expectedMembers), gApi.groups().id(group).members()); | |
| } | |
| private void assertMembers(Iterable<AccountInfo> members, String... expectedNames) { | |
| assertThat(Iterables.transform(members, i -> i.name)) | |
| .containsExactlyElementsIn(Arrays.asList(expectedNames)) | |
| .inOrder(); | |
| } | |
| private static void assertIncludes(List<GroupInfo> includes, String... expectedNames) { | |
| List<String> names = includes.stream().map(i -> i.name).collect(toImmutableList()); | |
| assertThat(names).containsExactlyElementsIn(Arrays.asList(expectedNames)); | |
| assertThat(names).isInOrder(); | |
| } | |
| private void assertBadRequest(ListRequest req) throws Exception { | |
| assertThrows(BadRequestException.class, () -> req.get()); | |
| } | |
| @Target({METHOD}) | |
| @Retention(RUNTIME) | |
| private @interface IgnoreGroupInconsistencies {} | |
| /** Checks if a group is indexed the correct number of times. */ | |
| private static class GroupIndexedCounter implements GroupIndexedListener { | |
| private final AtomicLongMap<String> countsByGroup = AtomicLongMap.create(); | |
| @Override | |
| public void onGroupIndexed(String uuid) { | |
| countsByGroup.incrementAndGet(uuid); | |
| } | |
| void clear() { | |
| countsByGroup.clear(); | |
| } | |
| void assertReindexOf(AccountGroup.UUID groupUuid) { | |
| assertReindexOf(ImmutableList.of(groupUuid)); | |
| } | |
| void assertReindexOf(List<AccountGroup.UUID> groupUuids) { | |
| Map<String, Long> expected = groupUuids.stream().collect(toMap(u -> u.get(), u -> 1L)); | |
| assertThat(countsByGroup.asMap()).containsExactlyEntriesIn(expected); | |
| clear(); | |
| } | |
| void assertNoReindex() { | |
| assertThat(countsByGroup.asMap()).isEmpty(); | |
| } | |
| } | |
| } |