diff --git a/java/com/google/gerrit/server/group/db/GroupConfig.java b/java/com/google/gerrit/server/group/db/GroupConfig.java index 421d46d1a7..4f45d15222 100644 --- a/java/com/google/gerrit/server/group/db/GroupConfig.java +++ b/java/com/google/gerrit/server/group/db/GroupConfig.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static java.util.stream.Collectors.joining; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; @@ -60,15 +61,14 @@ import org.eclipse.jgit.revwalk.RevSort; *

TODO(aliceks): expand docs. */ public class GroupConfig extends VersionedMetaData { - public static final String GROUP_CONFIG_FILE = "group.config"; - static final FooterKey FOOTER_ADD_MEMBER = new FooterKey("Add"); static final FooterKey FOOTER_REMOVE_MEMBER = new FooterKey("Remove"); static final FooterKey FOOTER_ADD_GROUP = new FooterKey("Add-group"); static final FooterKey FOOTER_REMOVE_GROUP = new FooterKey("Remove-group"); - private static final String MEMBERS_FILE = "members"; - private static final String SUBGROUPS_FILE = "subgroups"; + @VisibleForTesting public static final String GROUP_CONFIG_FILE = "group.config"; + @VisibleForTesting static final String MEMBERS_FILE = "members"; + @VisibleForTesting static final String SUBGROUPS_FILE = "subgroups"; private static final Pattern LINE_SEPARATOR_PATTERN = Pattern.compile("\\R"); private final AccountGroup.UUID groupUuid; diff --git a/java/com/google/gerrit/server/group/testing/BUILD b/java/com/google/gerrit/server/group/testing/BUILD new file mode 100644 index 0000000000..bde140bca4 --- /dev/null +++ b/java/com/google/gerrit/server/group/testing/BUILD @@ -0,0 +1,13 @@ +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "testing", + testonly = 1, + srcs = glob(["*.java"]), + deps = [ + "//java/com/google/gerrit/reviewdb:server", + "//java/com/google/gerrit/server", + "//lib:truth", + "//lib/jgit/org.eclipse.jgit:jgit", + ], +) diff --git a/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java b/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java new file mode 100644 index 0000000000..f0ab638255 --- /dev/null +++ b/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java @@ -0,0 +1,107 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.group.testing; + +import static com.google.common.truth.Truth.assertAbout; + +import com.google.common.truth.BooleanSubject; +import com.google.common.truth.ComparableSubject; +import com.google.common.truth.DefaultSubject; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.IterableSubject; +import com.google.common.truth.StringSubject; +import com.google.common.truth.Subject; +import com.google.common.truth.Truth; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.server.group.InternalGroup; +import java.sql.Timestamp; +import org.eclipse.jgit.lib.ObjectId; + +public class InternalGroupSubject extends Subject { + + public static InternalGroupSubject assertThat(InternalGroup group) { + return assertAbout(InternalGroupSubject::new).that(group); + } + + private InternalGroupSubject(FailureMetadata metadata, InternalGroup actual) { + super(metadata, actual); + } + + public ComparableSubject groupUuid() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getGroupUUID()).named("groupUuid"); + } + + public ComparableSubject nameKey() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getNameKey()).named("nameKey"); + } + + public StringSubject name() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getName()).named("name"); + } + + public DefaultSubject id() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getId()).named("id"); + } + + public StringSubject description() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getDescription()).named("description"); + } + + public ComparableSubject ownerGroupUuid() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getOwnerGroupUUID()).named("ownerGroupUuid"); + } + + public BooleanSubject visibleToAll() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.isVisibleToAll()).named("visibleToAll"); + } + + public ComparableSubject createdOn() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getCreatedOn()).named("createdOn"); + } + + public IterableSubject members() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getMembers()).named("members"); + } + + public IterableSubject subgroups() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getSubgroups()).named("subgroups"); + } + + public ComparableSubject refState() { + isNotNull(); + InternalGroup group = actual(); + return Truth.assertThat(group.getRefState()).named("refState"); + } +} diff --git a/javatests/com/google/gerrit/server/group/db/BUILD b/javatests/com/google/gerrit/server/group/db/BUILD index 2f2d78ff04..48e8d3034c 100644 --- a/javatests/com/google/gerrit/server/group/db/BUILD +++ b/javatests/com/google/gerrit/server/group/db/BUILD @@ -13,6 +13,7 @@ junit_tests( "//java/com/google/gerrit/reviewdb:server", "//java/com/google/gerrit/server", "//java/com/google/gerrit/server/group/db/testing", + "//java/com/google/gerrit/server/group/testing", "//java/com/google/gerrit/testing:gerrit-test-util", "//java/com/google/gerrit/truth", "//lib:gwtorm", diff --git a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java index 022228ace7..4effa942a1 100644 --- a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java +++ b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java @@ -15,18 +15,32 @@ package com.google.gerrit.server.group.db; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.gerrit.truth.OptionalSubject.assertThat; import static org.hamcrest.CoreMatchers.instanceOf; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.gerrit.common.Nullable; import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.common.data.GroupDescription; +import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.group.InternalGroup; +import com.google.gerrit.server.group.testing.InternalGroupSubject; +import com.google.gerrit.truth.OptionalSubject; import com.google.gwtorm.client.KeyUtil; import com.google.gwtorm.server.StandardKeyEncoder; +import java.io.IOException; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.ZoneId; import java.util.Optional; import java.util.TimeZone; import org.eclipse.jgit.errors.ConfigInvalidException; @@ -34,7 +48,10 @@ import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -55,6 +72,7 @@ public class GroupConfigTest { private final AccountGroup.Id groupId = new AccountGroup.Id(123); private final AuditLogFormatter auditLogFormatter = AuditLogFormatter.createBackedBy(ImmutableSet.of(), ImmutableSet.of(), "server-id"); + private final TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles"); @Before public void setUp() throws Exception { @@ -63,16 +81,36 @@ public class GroupConfigTest { } @Test - public void nameOfNewGroupMustNotBeNull() throws Exception { + public void specifiedGroupUuidIsRespectedForNewGroup() throws Exception { InternalGroupCreation groupCreation = - getPrefilledGroupCreationBuilder().setNameKey(new AccountGroup.NameKey(null)).build(); - GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build(); + createGroup(groupCreation); - try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { - expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Name of the group users-XYZ"); - groupConfig.commit(metaDataUpdate); - } + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().groupUuid().isEqualTo(groupUuid); + } + + @Test + public void specifiedNameIsRespectedForNewGroup() throws Exception { + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setNameKey(groupName).build(); + createGroup(groupCreation); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().nameKey().isEqualTo(groupName); + } + + @Test + public void nameOfGroupUpdateOverridesGroupCreation() throws Exception { + AccountGroup.NameKey anotherName = new AccountGroup.NameKey("Another name"); + + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setNameKey(groupName).build(); + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setName(anotherName).build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().nameKey().isEqualTo(anotherName); } @Test @@ -83,11 +121,33 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Name of the group users-XYZ"); + expectedException.expectMessage("Name of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } + @Test + public void nameOfNewGroupMustNotBeNull() throws Exception { + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setNameKey(new AccountGroup.NameKey(null)).build(); + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + + try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { + expectedException.expectCause(instanceOf(ConfigInvalidException.class)); + expectedException.expectMessage("Name of the group " + groupUuid); + groupConfig.commit(metaDataUpdate); + } + } + + @Test + public void specifiedIdIsRespectedForNewGroup() throws Exception { + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().setId(groupId).build(); + createGroup(groupCreation); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().id().isEqualTo(groupId); + } + @Test public void idOfNewGroupMustNotBeNegative() throws Exception { InternalGroupCreation groupCreation = @@ -96,13 +156,77 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("ID of the group users-XYZ"); + expectedException.expectMessage("ID of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } @Test - public void ownerUuidOfNewGroupMustNotBeNull() throws Exception { + public void descriptionDefaultsToNull() throws Exception { + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + createGroup(groupCreation); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().description().isNull(); + } + + @Test + public void specifiedDescriptionIsRespectedForNewGroup() throws Exception { + String description = "This is a test group."; + + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setDescription(description).build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().description().isEqualTo(description); + } + + @Test + public void emptyDescriptionForNewGroupIsIgnored() throws Exception { + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setDescription("").build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().description().isNull(); + } + + @Test + public void ownerGroupUuidDefaultsToGroupItself() throws Exception { + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + createGroup(groupCreation); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().ownerGroupUuid().isEqualTo(groupUuid); + } + + @Test + public void specifiedOwnerGroupUuidIsRespectedForNewGroup() throws Exception { + AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID("anotherOwnerUuid"); + + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setOwnerGroupUUID(ownerGroupUuid).build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().ownerGroupUuid().isEqualTo(ownerGroupUuid); + } + + @Test + public void ownerGroupUuidOfNewGroupMustNotBeNull() throws Exception { InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID(null)).build(); @@ -111,13 +235,13 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Owner UUID of the group users-XYZ"); + expectedException.expectMessage("Owner UUID of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } @Test - public void ownerUuidOfNewGroupMustNotBeEmpty() throws Exception { + public void ownerGroupUuidOfNewGroupMustNotBeEmpty() throws Exception { InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID("")).build(); @@ -126,25 +250,110 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Owner UUID of the group users-XYZ"); + expectedException.expectMessage("Owner UUID of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } + @Test + public void visibleToAllDefaultsToFalse() throws Exception { + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + createGroup(groupCreation); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().visibleToAll().isFalse(); + } + + @Test + public void specifiedVisibleToAllIsRespectedForNewGroup() throws Exception { + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setVisibleToAll(true).build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().visibleToAll().isTrue(); + } + + @Test + public void createdOnDefaultsToNow() throws Exception { + // Git timestamps are only precise to the second. + Timestamp testStart = TimeUtil.truncateToSecond(TimeUtil.nowTs()); + + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + createGroup(groupCreation); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().createdOn().isAtLeast(testStart); + } + + @Test + public void specifiedCreatedOnIsRespectedForNewGroup() throws Exception { + Timestamp createdOn = toTimestamp(LocalDate.of(2017, Month.DECEMBER, 11).atTime(13, 44, 10)); + + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setUpdatedOn(createdOn).build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().createdOn().isEqualTo(createdOn); + } + + @Test + public void specifiedMembersAreRespectedForNewGroup() throws Exception { + Account.Id member1 = new Account.Id(1); + Account.Id member2 = new Account.Id(2); + + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setMemberModification(members -> ImmutableSet.of(member1, member2)) + .build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().members().containsExactly(member1, member2); + } + + @Test + public void specifiedSubgroupsAreRespectedForNewGroup() throws Exception { + AccountGroup.UUID subgroup1 = new AccountGroup.UUID("subgroup1"); + AccountGroup.UUID subgroup2 = new AccountGroup.UUID("subgroup2"); + + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setSubgroupModification(subgroups -> ImmutableSet.of(subgroup1, subgroup2)) + .build(); + createGroup(groupCreation, groupUpdate); + + Optional group = loadGroup(groupCreation.getGroupUUID()); + assertThatGroup(group).value().subgroups().containsExactly(subgroup1, subgroup2); + } + @Test public void nameInConfigMayBeUndefined() throws Exception { populateGroupConfig(groupUuid, "[group]\n\tid = 42\n\townerGroupUuid = owners\n"); - GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); - assertThat(groupConfig.getLoadedGroup().get().getName()).isEmpty(); + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().name().isEmpty(); } @Test public void nameInConfigMayBeEmpty() throws Exception { populateGroupConfig(groupUuid, "[group]\n\tname=\n\tid = 42\n\townerGroupUuid = owners\n"); - GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); - assertThat(groupConfig.getLoadedGroup().get().getName()).isEmpty(); + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().name().isEmpty(); } @Test @@ -152,7 +361,7 @@ public class GroupConfigTest { populateGroupConfig(groupUuid, "[group]\n\tname = users\n\townerGroupUuid = owners\n"); expectedException.expect(ConfigInvalidException.class); - expectedException.expectMessage("ID of the group users-XYZ"); + expectedException.expectMessage("ID of the group " + groupUuid); GroupConfig.loadForGroup(repository, groupUuid); } @@ -162,23 +371,180 @@ public class GroupConfigTest { groupUuid, "[group]\n\tname = users\n\tid = -5\n\townerGroupUuid = owners\n"); expectedException.expect(ConfigInvalidException.class); - expectedException.expectMessage("ID of the group users-XYZ"); + expectedException.expectMessage("ID of the group " + groupUuid); GroupConfig.loadForGroup(repository, groupUuid); } @Test - public void ownerUuidInConfigMustBeDefined() throws Exception { + public void descriptionInConfigMayBeUndefined() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tid = 42\n\townerGroupUuid = owners\n"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().description().isNull(); + } + + @Test + public void descriptionInConfigMayBeEmpty() throws Exception { + populateGroupConfig( + groupUuid, "[group]\n\tdescription=\n\tid = 42\n\townerGroupUuid = owners\n"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().description().isNull(); + } + + @Test + public void ownerGroupUuidInConfigMustBeDefined() throws Exception { populateGroupConfig(groupUuid, "[group]\n\tname = users\n\tid = 42\n"); expectedException.expect(ConfigInvalidException.class); - expectedException.expectMessage("Owner UUID of the group users-XYZ"); + expectedException.expectMessage("Owner UUID of the group " + groupUuid); GroupConfig.loadForGroup(repository, groupUuid); } + @Test + public void membersFileNeedNotExist() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().members().isEmpty(); + } + + @Test + public void membersFileMayBeEmpty() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateSubgroupsFile(groupUuid, ""); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().members().isEmpty(); + } + + @Test + public void membersFileMayContainOnlyWhitespace() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateMembersFile(groupUuid, "\n\t\n\n"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().members().isEmpty(); + } + + @Test + public void membersFileMayUseAnyLineBreakCharacters() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateMembersFile(groupUuid, "1\n2\n3\r4\r\n5\u20296"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group) + .value() + .members() + .containsExactly( + new Account.Id(1), + new Account.Id(2), + new Account.Id(3), + new Account.Id(4), + new Account.Id(5), + new Account.Id(6)); + } + + @Test + public void membersFileMustContainIntegers() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateMembersFile(groupUuid, "One"); + + expectedException.expect(ConfigInvalidException.class); + expectedException.expectMessage("Invalid file members"); + loadGroup(groupUuid); + } + + @Test + public void membersFileUsesLineBreaksToSeparateMembers() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateMembersFile(groupUuid, "1\t2"); + + expectedException.expect(ConfigInvalidException.class); + expectedException.expectMessage("Invalid file members"); + loadGroup(groupUuid); + } + + @Test + public void subgroupsFileNeedNotExist() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().subgroups().isEmpty(); + } + + @Test + public void subgroupsFileMayBeEmpty() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateMembersFile(groupUuid, ""); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().subgroups().isEmpty(); + } + + @Test + public void subgroupsFileMayContainOnlyWhitespace() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateSubgroupsFile(groupUuid, "\n\t\n\n"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().subgroups().isEmpty(); + } + + @Test + public void subgroupsFileMayUseAnyLineBreakCharacters() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateSubgroupsFile(groupUuid, "1\n2\n3\r4\r\n5\u20296"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group) + .value() + .subgroups() + .containsExactly( + new AccountGroup.UUID("1"), + new AccountGroup.UUID("2"), + new AccountGroup.UUID("3"), + new AccountGroup.UUID("4"), + new AccountGroup.UUID("5"), + new AccountGroup.UUID("6")); + } + + @Test + public void subgroupsFileMayContainSubgroupsWithWhitespaceInUuid() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateSubgroupsFile(groupUuid, "1\t2 3"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().subgroups().containsExactly(new AccountGroup.UUID("1\t2 3")); + } + + @Test + public void subgroupsFileUsesLineBreaksToSeparateSubgroups() throws Exception { + populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n"); + populateSubgroupsFile(groupUuid, "1\t2\n3"); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group) + .value() + .subgroups() + .containsExactly(new AccountGroup.UUID("1\t2"), new AccountGroup.UUID("3")); + } + + @Test + public void nameCanBeUpdated() throws Exception { + createArbitraryGroup(groupUuid); + AccountGroup.NameKey newName = new AccountGroup.NameKey("New name"); + + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setName(newName).build(); + updateGroup(groupUuid, groupUpdate); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().nameKey().isEqualTo(newName); + } + @Test public void nameCannotBeUpdatedToNull() throws Exception { - populateGroupConfig( - groupUuid, "[group]\n\tname = users\n\tid = 42\n\townerGroupUuid = owners\n"); + createArbitraryGroup(groupUuid); GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); InternalGroupUpdate groupUpdate = @@ -187,15 +553,14 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Name of the group users-XYZ"); + expectedException.expectMessage("Name of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } @Test public void nameCannotBeUpdatedToEmptyString() throws Exception { - populateGroupConfig( - groupUuid, "[group]\n\tname = users\n\tid = 42\n\townerGroupUuid = owners\n"); + createArbitraryGroup(groupUuid); GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); InternalGroupUpdate groupUpdate = @@ -204,15 +569,65 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Name of the group users-XYZ"); + expectedException.expectMessage("Name of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } @Test - public void ownerUuidCannotBeUpdatedToNull() throws Exception { - populateGroupConfig( - groupUuid, "[group]\n\tname = users\n\tid = 42\n\townerGroupUuid = owners\n"); + public void nameCanBeUpdatedToEmptyStringIfExplicitlySpecified() throws Exception { + createArbitraryGroup(groupUuid); + AccountGroup.NameKey emptyName = new AccountGroup.NameKey(""); + + GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); + groupConfig.setAllowSaveEmptyName(); + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setName(emptyName).build(); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + commit(groupConfig); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().nameKey().isEqualTo(emptyName); + } + + @Test + public void descriptionCanBeUpdated() throws Exception { + createArbitraryGroup(groupUuid); + String newDescription = "New description"; + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setDescription(newDescription).build(); + updateGroup(groupUuid, groupUpdate); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().description().isEqualTo(newDescription); + } + + @Test + public void descriptionCanBeRemoved() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setDescription("").build(); + Optional group = updateGroup(groupUuid, groupUpdate); + + assertThatGroup(group).value().description().isNull(); + } + + @Test + public void ownerGroupUuidCanBeUpdated() throws Exception { + createArbitraryGroup(groupUuid); + AccountGroup.UUID newOwnerGroupUuid = new AccountGroup.UUID("New owner"); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setOwnerGroupUUID(newOwnerGroupUuid).build(); + updateGroup(groupUuid, groupUpdate); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().ownerGroupUuid().isEqualTo(newOwnerGroupUuid); + } + + @Test + public void ownerGroupUuidCannotBeUpdatedToNull() throws Exception { + createArbitraryGroup(groupUuid); GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); InternalGroupUpdate groupUpdate = @@ -221,15 +636,14 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Owner UUID of the group users-XYZ"); + expectedException.expectMessage("Owner UUID of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } @Test - public void ownerUuidCannotBeUpdatedToEmptyString() throws Exception { - populateGroupConfig( - groupUuid, "[group]\n\tname = users\n\tid = 42\n\townerGroupUuid = owners\n"); + public void ownerGroupUuidCannotBeUpdatedToEmptyString() throws Exception { + createArbitraryGroup(groupUuid); GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); InternalGroupUpdate groupUpdate = @@ -238,31 +652,894 @@ public class GroupConfigTest { try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { expectedException.expectCause(instanceOf(ConfigInvalidException.class)); - expectedException.expectMessage("Owner UUID of the group users-XYZ"); + expectedException.expectMessage("Owner UUID of the group " + groupUuid); groupConfig.commit(metaDataUpdate); } } @Test - public void automaticallyLoadedNewGroupDoesNotChangeOnReload() throws Exception { - InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); - GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); - try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { - groupConfig.commit(metaDataUpdate); - } + public void visibleToAllCanBeUpdated() throws Exception { + createArbitraryGroup(groupUuid); + boolean oldVisibleAll = loadGroup(groupUuid).map(InternalGroup::isVisibleToAll).orElse(false); - Optional createdGroup = groupConfig.getLoadedGroup(); - Optional reloadedGroup = - GroupConfig.loadForGroup(repository, groupCreation.getGroupUUID()).getLoadedGroup(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setVisibleToAll(!oldVisibleAll).build(); + updateGroup(groupUuid, groupUpdate); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().visibleToAll().isEqualTo(!oldVisibleAll); + } + + @Test + public void createdOnIsNotAffectedByFurtherUpdates() throws Exception { + Timestamp createdOn = toTimestamp(LocalDate.of(2017, Month.MAY, 11).atTime(13, 44, 10)); + Timestamp updatedOn = toTimestamp(LocalDate.of(2017, Month.DECEMBER, 12).atTime(10, 21, 49)); + + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate initialGroupUpdate = + InternalGroupUpdate.builder().setUpdatedOn(createdOn).build(); + createGroup(groupCreation, initialGroupUpdate); + + InternalGroupUpdate laterGroupUpdate = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(updatedOn) + .build(); + Optional group = updateGroup(groupCreation.getGroupUUID(), laterGroupUpdate); + + assertThatGroup(group).value().createdOn().isEqualTo(createdOn); + Optional reloadedGroup = loadGroup(groupUuid); + assertThatGroup(reloadedGroup).value().createdOn().isEqualTo(createdOn); + } + + @Test + public void membersCanBeAdded() throws Exception { + createArbitraryGroup(groupUuid); + Account.Id member1 = new Account.Id(1); + Account.Id member2 = new Account.Id(2); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder() + .setMemberModification(members -> ImmutableSet.of(member1)) + .build(); + updateGroup(groupUuid, groupUpdate1); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder() + .setMemberModification(members -> Sets.union(members, ImmutableSet.of(member2))) + .build(); + updateGroup(groupUuid, groupUpdate2); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().members().containsExactly(member1, member2); + } + + @Test + public void membersCanBeDeleted() throws Exception { + createArbitraryGroup(groupUuid); + Account.Id member1 = new Account.Id(1); + Account.Id member2 = new Account.Id(2); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder() + .setMemberModification(members -> ImmutableSet.of(member1, member2)) + .build(); + updateGroup(groupUuid, groupUpdate1); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder() + .setMemberModification(members -> Sets.difference(members, ImmutableSet.of(member1))) + .build(); + updateGroup(groupUuid, groupUpdate2); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().members().containsExactly(member2); + } + + @Test + public void subgroupsCanBeAdded() throws Exception { + createArbitraryGroup(groupUuid); + AccountGroup.UUID subgroup1 = new AccountGroup.UUID("subgroups1"); + AccountGroup.UUID subgroup2 = new AccountGroup.UUID("subgroups2"); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder() + .setSubgroupModification(subgroups -> ImmutableSet.of(subgroup1)) + .build(); + updateGroup(groupUuid, groupUpdate1); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder() + .setSubgroupModification(subgroups -> Sets.union(subgroups, ImmutableSet.of(subgroup2))) + .build(); + updateGroup(groupUuid, groupUpdate2); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().subgroups().containsExactly(subgroup1, subgroup2); + } + + @Test + public void subgroupsCanBeDeleted() throws Exception { + createArbitraryGroup(groupUuid); + AccountGroup.UUID subgroup1 = new AccountGroup.UUID("subgroups1"); + AccountGroup.UUID subgroup2 = new AccountGroup.UUID("subgroups2"); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder() + .setSubgroupModification(members -> ImmutableSet.of(subgroup1, subgroup2)) + .build(); + updateGroup(groupUuid, groupUpdate1); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder() + .setSubgroupModification( + members -> Sets.difference(members, ImmutableSet.of(subgroup1))) + .build(); + updateGroup(groupUuid, groupUpdate2); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().subgroups().containsExactly(subgroup2); + } + + @Test + public void createdGroupIsLoadedAutomatically() throws Exception { + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + Optional group = createGroup(groupCreation); + + assertThat(group).isPresent(); + } + + @Test + public void loadedNewGroupWithMandatoryPropertiesDoesNotChangeOnReload() throws Exception { + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + + Optional createdGroup = createGroup(groupCreation); + Optional reloadedGroup = loadGroup(groupCreation.getGroupUUID()); assertThat(createdGroup).isEqualTo(reloadedGroup); } - private InternalGroupCreation.Builder getPrefilledGroupCreationBuilder() { - return InternalGroupCreation.builder() - .setGroupUUID(groupUuid) - .setNameKey(groupName) - .setId(groupId); + @Test + public void loadedNewGroupWithAllPropertiesDoesNotChangeOnReload() throws Exception { + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setDescription("A test group") + .setOwnerGroupUUID(new AccountGroup.UUID("another owner")) + .setVisibleToAll(true) + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(new Timestamp(92900892)) + .setMemberModification(members -> ImmutableSet.of(new Account.Id(1), new Account.Id(2))) + .setSubgroupModification( + subgroups -> ImmutableSet.of(new AccountGroup.UUID("subgroup"))) + .build(); + + Optional createdGroup = createGroup(groupCreation, groupUpdate); + Optional reloadedGroup = loadGroup(groupCreation.getGroupUUID()); + + assertThat(createdGroup).isEqualTo(reloadedGroup); + } + + @Test + public void loadedGroupAfterUpdatesForAllPropertiesDoesNotChangeOnReload() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setDescription("A test group") + .setOwnerGroupUUID(new AccountGroup.UUID("another owner")) + .setVisibleToAll(true) + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(new Timestamp(92900892)) + .setMemberModification(members -> ImmutableSet.of(new Account.Id(1), new Account.Id(2))) + .setSubgroupModification( + subgroups -> ImmutableSet.of(new AccountGroup.UUID("subgroup"))) + .build(); + + Optional updatedGroup = updateGroup(groupUuid, groupUpdate); + Optional reloadedGroup = loadGroup(groupUuid); + + assertThat(updatedGroup).isEqualTo(reloadedGroup); + } + + @Test + public void loadedGroupWithAllPropertiesAndUpdateOfSinglePropertyDoesNotChangeOnReload() + throws Exception { + // Create a group with all properties set. + InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build(); + InternalGroupUpdate initialGroupUpdate = + InternalGroupUpdate.builder() + .setDescription("A test group") + .setOwnerGroupUUID(new AccountGroup.UUID("another owner")) + .setVisibleToAll(true) + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(new Timestamp(92900892)) + .setMemberModification(members -> ImmutableSet.of(new Account.Id(1), new Account.Id(2))) + .setSubgroupModification( + subgroups -> ImmutableSet.of(new AccountGroup.UUID("subgroup"))) + .build(); + createGroup(groupCreation, initialGroupUpdate); + + // Only update one of the properties. + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build(); + + Optional updatedGroup = updateGroup(groupCreation.getGroupUUID(), groupUpdate); + Optional reloadedGroup = loadGroup(groupCreation.getGroupUUID()); + + assertThat(updatedGroup).isEqualTo(reloadedGroup); + } + + @Test + public void groupConfigMayBeReusedForFurtherUpdates() throws Exception { + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).setId(groupId).build(); + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + commit(groupConfig); + + AccountGroup.NameKey name = new AccountGroup.NameKey("Robots"); + InternalGroupUpdate groupUpdate1 = InternalGroupUpdate.builder().setName(name).build(); + groupConfig.setGroupUpdate(groupUpdate1, auditLogFormatter); + commit(groupConfig); + + String description = "Test group for robots"; + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder().setDescription(description).build(); + groupConfig.setGroupUpdate(groupUpdate2, auditLogFormatter); + commit(groupConfig); + + Optional group = loadGroup(groupUuid); + assertThatGroup(group).value().id().isEqualTo(groupId); + assertThatGroup(group).value().nameKey().isEqualTo(name); + assertThatGroup(group).value().description().isEqualTo(description); + } + + @Test + public void newGroupIsRepresentedByARefPointingToARootCommit() throws Exception { + createArbitraryGroup(groupUuid); + + Ref ref = repository.exactRef(RefNames.refsGroups(groupUuid)); + assertThat(ref.getObjectId()).isNotNull(); + + try (RevWalk revWalk = new RevWalk(repository)) { + RevCommit revCommit = revWalk.parseCommit(ref.getObjectId()); + assertThat(revCommit.getParentCount()).isEqualTo(0); + } + } + + @Test + public void updatedGroupIsRepresentedByARefPointingToACommitSequence() throws Exception { + createArbitraryGroup(groupUuid); + + RevCommit commitAfterCreation = getLatestCommitForGroup(groupUuid); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + assertThat(commitAfterUpdate).isNotEqualTo(commitAfterCreation); + assertThat(commitAfterUpdate.getParents()).asList().containsExactly(commitAfterCreation); + } + + @Test + public void newCommitIsNotCreatedForEmptyUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().build(); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedForPureUpdatedOnUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + Timestamp updatedOn = toTimestamp(LocalDate.of(3017, Month.DECEMBER, 12).atTime(10, 21, 49)); + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setUpdatedOn(updatedOn).build(); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedForRedundantNameUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setName(groupName).build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedForRedundantDescriptionUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setDescription("A test group").build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedForRedundantVisibleToAllUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setVisibleToAll(true).build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedForRedundantOwnerGroupUuidUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setOwnerGroupUUID(new AccountGroup.UUID("Another owner")) + .build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedForRedundantMemberUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setMemberModification( + members -> Sets.union(members, ImmutableSet.of(new Account.Id(10)))) + .build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedForRedundantSubgroupsUpdate() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setSubgroupModification( + subgroups -> + Sets.union(subgroups, ImmutableSet.of(new AccountGroup.UUID("subgroup")))) + .build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid); + updateGroup(groupUuid, groupUpdate); + RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterUpdate).isEqualTo(commitBeforeUpdate); + } + + @Test + public void newCommitIsNotCreatedWhenCommittingGroupCreationTwice() throws Exception { + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build(); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build(); + + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + commit(groupConfig); + + RevCommit commitBeforeSecondCommit = getLatestCommitForGroup(groupUuid); + commit(groupConfig); + RevCommit commitAfterSecondCommit = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterSecondCommit).isEqualTo(commitBeforeSecondCommit); + } + + @Test + public void newCommitIsNotCreatedWhenCommittingGroupUpdateTwice() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setDescription("A test group").build(); + + GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + commit(groupConfig); + + RevCommit commitBeforeSecondCommit = getLatestCommitForGroup(groupUuid); + commit(groupConfig); + RevCommit commitAfterSecondCommit = getLatestCommitForGroup(groupUuid); + + assertThat(commitAfterSecondCommit).isEqualTo(commitBeforeSecondCommit); + } + + @Test + public void commitTimeMatchesDefaultCreatedOnOfNewGroup() throws Exception { + // Git timestamps are only precise to the second. + long testStartAsSecondsSinceEpoch = TimeUtil.nowTs().getTime() / 1000; + + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + createGroup(groupCreation); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getCommitTime()).isAtLeast((int) testStartAsSecondsSinceEpoch); + } + + @Test + public void commitTimeMatchesSpecifiedCreatedOnOfNewGroup() throws Exception { + // Git timestamps are only precise to the second. + long createdOnAsSecondsSinceEpoch = 9082093; + + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setUpdatedOn(new Timestamp(createdOnAsSecondsSinceEpoch * 1000)) + .build(); + createGroup(groupCreation, groupUpdate); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getCommitTime()).isEqualTo(createdOnAsSecondsSinceEpoch); + } + + @Test + public void timestampOfCommitterMatchesSpecifiedCreatedOnOfNewGroup() throws Exception { + Timestamp committerTimestamp = + toTimestamp(LocalDate.of(2017, Month.DECEMBER, 13).atTime(15, 5, 27)); + Timestamp createdOn = toTimestamp(LocalDate.of(2016, Month.MARCH, 11).atTime(23, 49, 11)); + + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(createdOn) + .build(); + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + + PersonIdent committerIdent = + new PersonIdent("Jane", "Jane@gerritcodereview.com", committerTimestamp, timeZone); + try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { + metaDataUpdate.getCommitBuilder().setCommitter(committerIdent); + groupConfig.commit(metaDataUpdate); + } + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getCommitterIdent().getWhen()).isEqualTo(createdOn); + assertThat(revCommit.getCommitterIdent().getTimeZone().getRawOffset()) + .isEqualTo(timeZone.getRawOffset()); + } + + @Test + public void timestampOfAuthorMatchesSpecifiedCreatedOnOfNewGroup() throws Exception { + Timestamp authorTimestamp = + toTimestamp(LocalDate.of(2017, Month.DECEMBER, 13).atTime(15, 5, 27)); + Timestamp createdOn = toTimestamp(LocalDate.of(2016, Month.MARCH, 11).atTime(23, 49, 11)); + + InternalGroupCreation groupCreation = + InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId) + .build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(createdOn) + .build(); + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + + PersonIdent authorIdent = + new PersonIdent("Jane", "Jane@gerritcodereview.com", authorTimestamp, timeZone); + try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { + metaDataUpdate.getCommitBuilder().setAuthor(authorIdent); + groupConfig.commit(metaDataUpdate); + } + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getAuthorIdent().getWhen()).isEqualTo(createdOn); + assertThat(revCommit.getAuthorIdent().getTimeZone().getRawOffset()) + .isEqualTo(timeZone.getRawOffset()); + } + + @Test + public void commitTimeMatchesDefaultUpdatedOnOfUpdatedGroup() throws Exception { + // Git timestamps are only precise to the second. + long testStartAsSecondsSinceEpoch = TimeUtil.nowTs().getTime() / 1000; + + createArbitraryGroup(groupUuid); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getCommitTime()).isAtLeast((int) testStartAsSecondsSinceEpoch); + } + + @Test + public void commitTimeMatchesSpecifiedUpdatedOnOfUpdatedGroup() throws Exception { + // Git timestamps are only precise to the second. + long updatedOnAsSecondsSinceEpoch = 9082093; + + createArbitraryGroup(groupUuid); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(new Timestamp(updatedOnAsSecondsSinceEpoch * 1000)) + .build(); + updateGroup(groupUuid, groupUpdate); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getCommitTime()).isEqualTo(updatedOnAsSecondsSinceEpoch); + } + + @Test + public void timestampOfCommitterMatchesSpecifiedUpdatedOnOfUpdatedGroup() throws Exception { + Timestamp committerTimestamp = + toTimestamp(LocalDate.of(2017, Month.DECEMBER, 13).atTime(15, 5, 27)); + Timestamp updatedOn = toTimestamp(LocalDate.of(2016, Month.MARCH, 11).atTime(23, 49, 11)); + + createArbitraryGroup(groupUuid); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(updatedOn) + .build(); + GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + + PersonIdent committerIdent = + new PersonIdent("Jane", "Jane@gerritcodereview.com", committerTimestamp, timeZone); + try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { + metaDataUpdate.getCommitBuilder().setCommitter(committerIdent); + groupConfig.commit(metaDataUpdate); + } + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getCommitterIdent().getWhen()).isEqualTo(updatedOn); + assertThat(revCommit.getCommitterIdent().getTimeZone().getRawOffset()) + .isEqualTo(timeZone.getRawOffset()); + } + + @Test + public void timestampOfAuthorMatchesSpecifiedUpdatedOnOfUpdatedGroup() throws Exception { + Timestamp authorTimestamp = + toTimestamp(LocalDate.of(2017, Month.DECEMBER, 13).atTime(15, 5, 27)); + Timestamp updatedOn = toTimestamp(LocalDate.of(2016, Month.MARCH, 11).atTime(23, 49, 11)); + + createArbitraryGroup(groupUuid); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("Another name")) + .setUpdatedOn(updatedOn) + .build(); + GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + + PersonIdent authorIdent = + new PersonIdent("Jane", "Jane@gerritcodereview.com", authorTimestamp, timeZone); + try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { + metaDataUpdate.getCommitBuilder().setAuthor(authorIdent); + groupConfig.commit(metaDataUpdate); + } + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getAuthorIdent().getWhen()).isEqualTo(updatedOn); + assertThat(revCommit.getAuthorIdent().getTimeZone().getRawOffset()) + .isEqualTo(timeZone.getRawOffset()); + } + + @Test + public void refStateOfLoadedGroupIsPopulatedWithCommitSha1() throws Exception { + createArbitraryGroup(groupUuid); + + Optional group = loadGroup(groupUuid); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThatGroup(group).value().refState().isEqualTo(revCommit.copy()); + } + + @Test + public void groupCanBeLoadedAtASpecificRevision() throws Exception { + createArbitraryGroup(groupUuid); + + AccountGroup.NameKey firstName = new AccountGroup.NameKey("Bots"); + InternalGroupUpdate groupUpdate1 = InternalGroupUpdate.builder().setName(firstName).build(); + updateGroup(groupUuid, groupUpdate1); + + RevCommit commitAfterUpdate1 = getLatestCommitForGroup(groupUuid); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Robots")).build(); + updateGroup(groupUuid, groupUpdate2); + + GroupConfig groupConfig = + GroupConfig.loadForGroupSnapshot(repository, groupUuid, commitAfterUpdate1.copy()); + Optional group = groupConfig.getLoadedGroup(); + assertThatGroup(group).value().nameKey().isEqualTo(firstName); + assertThatGroup(group).value().refState().isEqualTo(commitAfterUpdate1.copy()); + } + + @Test + public void commitMessageOfNewGroupWithoutMembersOrSubgroupsContainsNoFooters() throws Exception { + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build(); + createGroup(groupCreation); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()).isEqualTo("Create group"); + } + + @Test + public void commitMessageOfNewGroupWithAdditionalNameSpecificationContainsNoFooters() + throws Exception { + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build(); + createGroup(groupCreation, groupUpdate); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()).isEqualTo("Create group"); + } + + @Test + public void commitMessageOfNewGroupWithMembersContainsFooters() throws Exception { + Account account13 = createAccount(new Account.Id(13), "John"); + Account account7 = createAccount(new Account.Id(7), "Jane"); + ImmutableSet accounts = ImmutableSet.of(account13, account7); + + AuditLogFormatter auditLogFormatter = + AuditLogFormatter.createBackedBy(accounts, ImmutableSet.of(), "server-id"); + + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setMemberModification(members -> ImmutableSet.of(account13.getId(), account7.getId())) + .build(); + + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + commit(groupConfig); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()) + .isEqualTo("Create group\n\nAdd: John <13@server-id>\nAdd: Jane <7@server-id>"); + } + + @Test + public void commitMessageOfNewGroupWithSubgroupsContainsFooters() throws Exception { + GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots"); + GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers"); + ImmutableSet groups = ImmutableSet.of(group1, group2); + + AuditLogFormatter auditLogFormatter = + AuditLogFormatter.createBackedBy(ImmutableSet.of(), groups, "serverId"); + + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build(); + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setSubgroupModification( + subgroups -> ImmutableSet.of(group1.getGroupUUID(), group2.getGroupUUID())) + .build(); + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + commit(groupConfig); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()) + .isEqualTo("Create group\n\nAdd-group: Bots <129403>\nAdd-group: Verifiers <8903493>"); + } + + @Test + public void commitMessageOfMemberAdditionContainsFooters() throws Exception { + Account account13 = createAccount(new Account.Id(13), "John"); + Account account7 = createAccount(new Account.Id(7), "Jane"); + ImmutableSet accounts = ImmutableSet.of(account13, account7); + + createArbitraryGroup(groupUuid); + + AuditLogFormatter auditLogFormatter = + AuditLogFormatter.createBackedBy(accounts, ImmutableSet.of(), "GerritServer1"); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setMemberModification(members -> ImmutableSet.of(account13.getId(), account7.getId())) + .build(); + updateGroup(groupUuid, groupUpdate, auditLogFormatter); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()) + .isEqualTo("Update group\n\nAdd: John <13@GerritServer1>\nAdd: Jane <7@GerritServer1>"); + } + + @Test + public void commitMessageOfMemberRemovalContainsFooters() throws Exception { + Account account13 = createAccount(new Account.Id(13), "John"); + Account account7 = createAccount(new Account.Id(7), "Jane"); + ImmutableSet accounts = ImmutableSet.of(account13, account7); + + createArbitraryGroup(groupUuid); + + AuditLogFormatter auditLogFormatter = + AuditLogFormatter.createBackedBy(accounts, ImmutableSet.of(), "server-id"); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder() + .setMemberModification(members -> ImmutableSet.of(account13.getId(), account7.getId())) + .build(); + updateGroup(groupUuid, groupUpdate1, auditLogFormatter); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder() + .setMemberModification(members -> ImmutableSet.of(account7.getId())) + .build(); + updateGroup(groupUuid, groupUpdate2, auditLogFormatter); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()).isEqualTo("Update group\n\nRemove: John <13@server-id>"); + } + + @Test + public void commitMessageOfSubgroupAdditionContainsFooters() throws Exception { + GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots"); + GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers"); + ImmutableSet groups = ImmutableSet.of(group1, group2); + + createArbitraryGroup(groupUuid); + + AuditLogFormatter auditLogFormatter = + AuditLogFormatter.createBackedBy(ImmutableSet.of(), groups, "serverId"); + + InternalGroupUpdate groupUpdate = + InternalGroupUpdate.builder() + .setSubgroupModification( + subgroups -> ImmutableSet.of(group1.getGroupUUID(), group2.getGroupUUID())) + .build(); + updateGroup(groupUuid, groupUpdate, auditLogFormatter); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()) + .isEqualTo("Update group\n\nAdd-group: Bots <129403>\nAdd-group: Verifiers <8903493>"); + } + + @Test + public void commitMessageOfSubgroupRemovalContainsFooters() throws Exception { + GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots"); + GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers"); + ImmutableSet groups = ImmutableSet.of(group1, group2); + + createArbitraryGroup(groupUuid); + + AuditLogFormatter auditLogFormatter = + AuditLogFormatter.createBackedBy(ImmutableSet.of(), groups, "serverId"); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder() + .setSubgroupModification( + subgroups -> ImmutableSet.of(group1.getGroupUUID(), group2.getGroupUUID())) + .build(); + updateGroup(groupUuid, groupUpdate1, auditLogFormatter); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder() + .setSubgroupModification(subgroups -> ImmutableSet.of(group1.getGroupUUID())) + .build(); + updateGroup(groupUuid, groupUpdate2, auditLogFormatter); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()) + .isEqualTo("Update group\n\nRemove-group: Verifiers <8903493>"); + } + + @Test + public void commitMessageOfGroupRenameContainsFooters() throws Exception { + createArbitraryGroup(groupUuid); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Old name")).build(); + updateGroup(groupUuid, groupUpdate1); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("New name")).build(); + updateGroup(groupUuid, groupUpdate2); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()) + .isEqualTo("Update group\n\nRename from Old name to New name"); + } + + @Test + public void commitMessageFootersCanBeMixed() throws Exception { + Account account13 = createAccount(new Account.Id(13), "John"); + Account account7 = createAccount(new Account.Id(7), "Jane"); + ImmutableSet accounts = ImmutableSet.of(account13, account7); + GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots"); + GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers"); + ImmutableSet groups = ImmutableSet.of(group1, group2); + + createArbitraryGroup(groupUuid); + + AuditLogFormatter auditLogFormatter = + AuditLogFormatter.createBackedBy(accounts, groups, "serverId"); + + InternalGroupUpdate groupUpdate1 = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("Old name")) + .setMemberModification(members -> ImmutableSet.of(account7.getId())) + .setSubgroupModification(subgroups -> ImmutableSet.of(group2.getGroupUUID())) + .build(); + updateGroup(groupUuid, groupUpdate1, auditLogFormatter); + + InternalGroupUpdate groupUpdate2 = + InternalGroupUpdate.builder() + .setName(new AccountGroup.NameKey("New name")) + .setMemberModification(members -> ImmutableSet.of(account13.getId())) + .setSubgroupModification(subgroups -> ImmutableSet.of(group1.getGroupUUID())) + .build(); + updateGroup(groupUuid, groupUpdate2, auditLogFormatter); + + RevCommit revCommit = getLatestCommitForGroup(groupUuid); + assertThat(revCommit.getFullMessage()) + .isEqualTo( + "Update group\n" + + "\n" + + "Rename from Old name to New name\n" + + "Remove: Jane <7@serverId>\n" + + "Add: John <13@serverId>\n" + + "Remove-group: Verifiers <8903493>\n" + + "Add-group: Bots <129403>"); + } + + private static Timestamp toTimestamp(LocalDateTime localDateTime) { + return Timestamp.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } private void populateGroupConfig(AccountGroup.UUID uuid, String fileContent) throws Exception { @@ -274,10 +1551,81 @@ public class GroupConfigTest { .create(); } + private void populateMembersFile(AccountGroup.UUID uuid, String fileContent) throws Exception { + testRepository + .branch(RefNames.refsGroups(uuid)) + .commit() + .message("Prepopulate members") + .add(GroupConfig.MEMBERS_FILE, fileContent) + .create(); + } + + private void populateSubgroupsFile(AccountGroup.UUID uuid, String fileContent) throws Exception { + testRepository + .branch(RefNames.refsGroups(uuid)) + .commit() + .message("Prepopulate subgroups") + .add(GroupConfig.SUBGROUPS_FILE, fileContent) + .create(); + } + + private void createArbitraryGroup(AccountGroup.UUID uuid) throws Exception { + InternalGroupCreation groupCreation = + getPrefilledGroupCreationBuilder().setGroupUUID(uuid).build(); + createGroup(groupCreation); + } + + private InternalGroupCreation.Builder getPrefilledGroupCreationBuilder() { + return InternalGroupCreation.builder() + .setGroupUUID(groupUuid) + .setNameKey(groupName) + .setId(groupId); + } + + private Optional createGroup(InternalGroupCreation groupCreation) + throws Exception { + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + commit(groupConfig); + return groupConfig.getLoadedGroup(); + } + + private Optional createGroup( + InternalGroupCreation groupCreation, InternalGroupUpdate groupUpdate) throws Exception { + GroupConfig groupConfig = GroupConfig.createForNewGroup(repository, groupCreation); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + commit(groupConfig); + return groupConfig.getLoadedGroup(); + } + + private Optional updateGroup( + AccountGroup.UUID uuid, InternalGroupUpdate groupUpdate) throws Exception { + return updateGroup(uuid, groupUpdate, auditLogFormatter); + } + + private Optional updateGroup( + AccountGroup.UUID uuid, InternalGroupUpdate groupUpdate, AuditLogFormatter auditLogFormatter) + throws Exception { + GroupConfig groupConfig = GroupConfig.loadForGroup(repository, uuid); + groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter); + commit(groupConfig); + return groupConfig.getLoadedGroup(); + } + + private Optional loadGroup(AccountGroup.UUID uuid) throws Exception { + GroupConfig groupConfig = GroupConfig.loadForGroup(repository, uuid); + return groupConfig.getLoadedGroup(); + } + + private void commit(GroupConfig groupConfig) throws IOException { + try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) { + groupConfig.commit(metaDataUpdate); + } + } + private MetaDataUpdate createMetaDataUpdate() { - TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); PersonIdent serverIdent = - new PersonIdent("Gerrit Server", "noreply@gerritcodereview.com", TimeUtil.nowTs(), tz); + new PersonIdent( + "Gerrit Server", "noreply@gerritcodereview.com", TimeUtil.nowTs(), timeZone); MetaDataUpdate metaDataUpdate = new MetaDataUpdate( @@ -286,4 +1634,52 @@ public class GroupConfigTest { metaDataUpdate.getCommitBuilder().setAuthor(serverIdent); return metaDataUpdate; } + + private RevCommit getLatestCommitForGroup(AccountGroup.UUID uuid) throws IOException { + Ref ref = repository.exactRef(RefNames.refsGroups(uuid)); + assertWithMessage("Precondition: Assumed that ref for group " + uuid + " exists.") + .that(ref.getObjectId()) + .isNotNull(); + + try (RevWalk revWalk = new RevWalk(repository)) { + return revWalk.parseCommit(ref.getObjectId()); + } + } + + private static Account createAccount(Account.Id id, String name) { + Account account = new Account(id, TimeUtil.nowTs()); + account.setFullName(name); + return account; + } + + private static GroupDescription.Basic createGroup(AccountGroup.UUID uuid, String name) { + return new GroupDescription.Basic() { + @Override + public AccountGroup.UUID getGroupUUID() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + @Nullable + @Override + public String getEmailAddress() { + return null; + } + + @Nullable + @Override + public String getUrl() { + return null; + } + }; + } + + private static OptionalSubject assertThatGroup( + Optional loadedGroup) { + return assertThat(loadedGroup, InternalGroupSubject::assertThat); + } }