| // 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.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.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| 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(); |
| 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"))); |
| } |
| |
| @Test |
| public void createdOnFieldIsPopulatedForNewGroup() throws Exception { |
| // NoteDb allows only second precision. |
| Timestamp testStartTime = TimeUtil.truncateToSecond(TimeUtil.nowTs()); |
| String newGroupName = name("newGroup"); |
| GroupInfo group = gApi.groups().create(newGroupName).get(); |
| |
| assertThat(group.createdOn).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 |
| @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()); |
| 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 pushToGroupBranchForReviewForAllUsersRepoIsRejectedOnSubmit() throws Throwable { |
| pushToGroupBranchForReviewAndSubmit( |
| allUsers, RefNames.refsGroups(adminGroupUuid()), "group update not allowed"); |
| } |
| |
| @Test |
| public void pushToGroupBranchForReviewForNonAllUsersRepoAndSubmit() throws Throwable { |
| String groupRef = RefNames.refsGroups(adminGroupUuid()); |
| createBranch(project, groupRef); |
| pushToGroupBranchForReviewAndSubmit(project, groupRef, 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); |
| 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); |
| 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); |
| 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"); |
| 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); |
| 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 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", "group.config", "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("group update not allowed"); |
| } 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); |
| 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.nowTs()); |
| 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(); |
| } |
| } |
| } |