blob: 78fef3932ccad9e3e5dc0a67d151c7a80064318c [file] [log] [blame]
// Copyright (C) 2018 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.schema;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;
import static com.google.gerrit.server.notedb.NoteDbTable.GROUPS;
import static com.google.gerrit.server.notedb.NotesMigration.DISABLE_REVIEW_DB;
import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GroupAuditEventInfo;
import com.google.gerrit.extensions.common.GroupAuditEventInfo.GroupMemberAuditEventInfo;
import com.google.gerrit.extensions.common.GroupAuditEventInfo.Type;
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.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupUUID;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.config.GerritServerIdProvider;
import com.google.gerrit.server.git.CommitUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.group.db.GroupConfig;
import com.google.gerrit.server.group.db.GroupNameNotes;
import com.google.gerrit.server.group.db.GroupsConsistencyChecker;
import com.google.gerrit.server.group.testing.InternalGroupSubject;
import com.google.gerrit.server.group.testing.TestGroupBackend;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.InMemoryTestEnvironment;
import com.google.gerrit.testing.TestTimeUtil;
import com.google.gerrit.testing.TestTimeUtil.TempClockStep;
import com.google.gerrit.testing.TestUpdateUI;
import com.google.gerrit.truth.OptionalSubject;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
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.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class Schema_166_to_167_WithGroupsInReviewDbTest {
private static Config createConfig() {
Config config = new Config();
config.setString(GerritServerIdProvider.SECTION, null, GerritServerIdProvider.KEY, "1234567");
// Enable groups in ReviewDb. This means the primary storage for groups is ReviewDb.
config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), DISABLE_REVIEW_DB, false);
return config;
}
@Rule
public InMemoryTestEnvironment testEnv =
new InMemoryTestEnvironment(Schema_166_to_167_WithGroupsInReviewDbTest::createConfig);
@Inject private GerritApi gApi;
@Inject private Schema_167 schema167;
@Inject private ReviewDb db;
@Inject private GitRepositoryManager gitRepoManager;
@Inject private AllUsersName allUsersName;
@Inject private GroupsConsistencyChecker consistencyChecker;
@Inject private IdentifiedUser currentUser;
@Inject private @GerritServerId String serverId;
@Inject private @GerritPersonIdent PersonIdent serverIdent;
@Inject private GroupBundle.Factory groupBundleFactory;
@Inject private GroupBackend groupBackend;
@Inject private DynamicSet<GroupBackend> backends;
@Inject private Sequences seq;
private JdbcSchema jdbcSchema;
@Before
public void initDb() throws Exception {
jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
try (Statement stmt = jdbcSchema.getConnection().createStatement()) {
stmt.execute(
"CREATE TABLE account_groups ("
+ " group_uuid varchar(255) DEFAULT '' NOT NULL,"
+ " group_id INTEGER DEFAULT 0 NOT NULL,"
+ " name varchar(255) DEFAULT '' NOT NULL,"
+ " created_on TIMESTAMP,"
+ " description CLOB,"
+ " owner_group_uuid varchar(255) DEFAULT '' NOT NULL,"
+ " visible_to_all CHAR(1) DEFAULT 'N' NOT NULL"
+ ")");
stmt.execute(
"CREATE TABLE account_group_members ("
+ " group_id INTEGER DEFAULT 0 NOT NULL,"
+ " account_id INTEGER DEFAULT 0 NOT NULL"
+ ")");
stmt.execute(
"CREATE TABLE account_group_members_audit ("
+ " group_id INTEGER DEFAULT 0 NOT NULL,"
+ " account_id INTEGER DEFAULT 0 NOT NULL,"
+ " added_by INTEGER DEFAULT 0 NOT NULL,"
+ " added_on TIMESTAMP,"
+ " removed_by INTEGER,"
+ " removed_on TIMESTAMP"
+ ")");
stmt.execute(
"CREATE TABLE account_group_by_id ("
+ " group_id INTEGER DEFAULT 0 NOT NULL,"
+ " include_uuid VARCHAR(255) DEFAULT '' NOT NULL"
+ ")");
stmt.execute(
"CREATE TABLE account_group_by_id_aud ("
+ " group_id INTEGER DEFAULT 0 NOT NULL,"
+ " include_uuid VARCHAR(255) DEFAULT '' NOT NULL,"
+ " added_by INTEGER DEFAULT 0 NOT NULL,"
+ " added_on TIMESTAMP,"
+ " removed_by INTEGER,"
+ " removed_on TIMESTAMP"
+ ")");
}
}
@Before
public void setTimeForTesting() {
TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
}
@After
public void resetTime() {
TestTimeUtil.useSystemTime();
}
@Test
public void reviewDbOnlyGroupsAreMigratedToNoteDb() throws Exception {
// Create groups only in ReviewDb
AccountGroup group1 = newGroup().setName("verifiers").build();
AccountGroup group2 = newGroup().setName("contributors").build();
storeInReviewDb(group1, group2);
executeSchemaMigration(schema167, group1, group2);
ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
ImmutableList<String> groupNames =
groups.stream().map(GroupReference::getName).collect(toImmutableList());
assertThat(groupNames).containsAllOf("verifiers", "contributors");
}
@Test
public void alreadyExistingGroupsAreMigratedToNoteDb() throws Exception {
// Create group in NoteDb and ReviewDb
GroupInput groupInput = new GroupInput();
groupInput.name = "verifiers";
groupInput.description = "old";
GroupInfo group1 = gApi.groups().create(groupInput).get();
storeInReviewDb(group1);
// Update group only in ReviewDb
AccountGroup group1InReviewDb = getFromReviewDb(new AccountGroup.Id(group1.groupId));
group1InReviewDb.setDescription("new");
updateInReviewDb(group1InReviewDb);
// Create a second group in NoteDb and ReviewDb
GroupInfo group2 = gApi.groups().create("contributors").get();
storeInReviewDb(group2);
executeSchemaMigration(schema167, group1, group2);
// Verify that both groups are present in NoteDb
ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
ImmutableList<String> groupNames =
groups.stream().map(GroupReference::getName).collect(toImmutableList());
assertThat(groupNames).containsAllOf("verifiers", "contributors");
// Verify that group1 has the description from ReviewDb
Optional<InternalGroup> group1InNoteDb = getGroupFromNoteDb(new AccountGroup.UUID(group1.id));
assertThatGroup(group1InNoteDb).value().description().isEqualTo("new");
}
@Test
public void adminGroupIsMigratedToNoteDb() throws Exception {
// Administrators group is automatically created for all Gerrit servers (NoteDb only).
GroupInfo adminGroup = gApi.groups().id("Administrators").get();
storeInReviewDb(adminGroup);
executeSchemaMigration(schema167, adminGroup);
ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
ImmutableList<String> groupNames =
groups.stream().map(GroupReference::getName).collect(toImmutableList());
assertThat(groupNames).contains("Administrators");
}
@Test
public void nonInteractiveUsersGroupIsMigratedToNoteDb() throws Exception {
// 'Non-Interactive Users' group is automatically created for all Gerrit servers (NoteDb only).
GroupInfo nonInteractiveUsersGroup = gApi.groups().id("Non-Interactive Users").get();
storeInReviewDb(nonInteractiveUsersGroup);
executeSchemaMigration(schema167, nonInteractiveUsersGroup);
ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
ImmutableList<String> groupNames =
groups.stream().map(GroupReference::getName).collect(toImmutableList());
assertThat(groupNames).contains("Non-Interactive Users");
}
@Test
public void groupsAreConsistentAfterMigrationToNoteDb() throws Exception {
// Administrators group are automatically created for all Gerrit servers (NoteDb only).
GroupInfo adminGroup = gApi.groups().id("Administrators").get();
GroupInfo nonInteractiveUsersGroup = gApi.groups().id("Non-Interactive Users").get();
storeInReviewDb(adminGroup, nonInteractiveUsersGroup);
AccountGroup group1 = newGroup().setName("verifiers").build();
AccountGroup group2 = newGroup().setName("contributors").build();
storeInReviewDb(group1, group2);
executeSchemaMigration(schema167, group1, group2);
List<ConsistencyCheckInfo.ConsistencyProblemInfo> consistencyProblems =
consistencyChecker.check();
assertThat(consistencyProblems).isEmpty();
}
@Test
public void nameIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup group = newGroup().setName("verifiers").build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().name().isEqualTo("verifiers");
}
@Test
public void emptyNameIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup group = newGroup().setName("").build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().name().isEqualTo("");
}
@Test
public void uuidIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup.UUID groupUuid = new AccountGroup.UUID("ABCDEF");
AccountGroup group = newGroup().setGroupUuid(groupUuid).build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(groupUuid);
assertThatGroup(groupInNoteDb).value().groupUuid().isEqualTo(groupUuid);
}
@Test
public void idIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup.Id id = new AccountGroup.Id(12345);
AccountGroup group = newGroup().setId(id).build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().id().isEqualTo(id);
}
@Test
public void createdOnIsKeptDuringMigrationToNoteDb() throws Exception {
Timestamp createdOn =
Timestamp.from(
LocalDate.of(2018, Month.FEBRUARY, 20)
.atTime(18, 2, 56)
.atZone(ZoneOffset.UTC)
.toInstant());
AccountGroup group = newGroup().setCreatedOn(createdOn).build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().createdOn().isEqualTo(createdOn);
}
@Test
public void ownerUuidIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID("UVWXYZ");
AccountGroup group = newGroup().setOwnerGroupUuid(ownerGroupUuid).build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().ownerGroupUuid().isEqualTo(ownerGroupUuid);
}
@Test
public void descriptionIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup group = newGroup().setDescription("A test group").build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().description().isEqualTo("A test group");
}
@Test
public void absentDescriptionIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup group = newGroup().build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().description().isNull();
}
@Test
public void visibleToAllIsKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup group = newGroup().setVisibleToAll(true).build();
storeInReviewDb(group);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().visibleToAll().isTrue();
}
@Test
public void membersAreKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup group = newGroup().build();
storeInReviewDb(group);
Account.Id member1 = new Account.Id(23456);
Account.Id member2 = new Account.Id(93483);
addMembersInReviewDb(group.getId(), member1, member2);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().members().containsExactly(member1, member2);
}
@Test
public void subgroupsAreKeptDuringMigrationToNoteDb() throws Exception {
AccountGroup group = newGroup().build();
storeInReviewDb(group);
AccountGroup.UUID subgroup1 = new AccountGroup.UUID("FGHIKL");
AccountGroup.UUID subgroup2 = new AccountGroup.UUID("MNOPQR");
addSubgroupsInReviewDb(group.getId(), subgroup1, subgroup2);
executeSchemaMigration(schema167, group);
Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
assertThatGroup(groupInNoteDb).value().subgroups().containsExactly(subgroup1, subgroup2);
}
@Test
public void logFormatWithAccountsAndGerritGroups() throws Exception {
AccountInfo user1 = createAccount("user1");
AccountInfo user2 = createAccount("user2");
AccountGroup group1 = createInReviewDb("group1");
AccountGroup group2 = createInReviewDb("group2");
AccountGroup group3 = createInReviewDb("group3");
// Add some accounts
try (TempClockStep step = TestTimeUtil.freezeClock()) {
addMembersInReviewDb(
group1.getId(), new Account.Id(user1._accountId), new Account.Id(user2._accountId));
}
TimeUtil.nowTs();
// Add some Gerrit groups
try (TempClockStep step = TestTimeUtil.freezeClock()) {
addSubgroupsInReviewDb(group1.getId(), group2.getGroupUUID(), group3.getGroupUUID());
}
executeSchemaMigration(schema167, group1, group2, group3);
GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group1.getGroupUUID());
ImmutableList<CommitInfo> log = log(group1);
assertThat(log).hasSize(4);
// Verify commit that created the group
assertThat(log.get(0)).message().isEqualTo("Create group");
assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
// Verify commit that the group creator as member
assertThat(log.get(1))
.message()
.isEqualTo(
"Update group\n\nAdd: "
+ currentUser.getName()
+ " <"
+ currentUser.getAccountId()
+ "@"
+ serverId
+ ">");
assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
// Verify commit that added members
assertThat(log.get(2))
.message()
.isEqualTo(
"Update group\n"
+ "\n"
+ ("Add: user1 <" + user1._accountId + "@" + serverId + ">\n")
+ ("Add: user2 <" + user2._accountId + "@" + serverId + ">"));
assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
// Verify commit that added Gerrit groups
assertThat(log.get(3))
.message()
.isEqualTo(
"Update group\n"
+ "\n"
+ ("Add-group: " + group2.getName() + " <" + group2.getGroupUUID().get() + ">\n")
+ ("Add-group: " + group3.getName() + " <" + group3.getGroupUUID().get() + ">"));
assertThat(log.get(3)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(3)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(3)).committer().hasSameDateAs(log.get(3).author);
// Verify that audit log is correctly read by Gerrit
List<? extends GroupAuditEventInfo> auditEvents =
gApi.groups().id(group1.getGroupUUID().get()).auditLog();
assertThat(auditEvents).hasSize(5);
AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
assertMemberAuditEvent(
auditEvents.get(4), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
assertMemberAuditEvents(
auditEvents.get(3),
auditEvents.get(2),
Type.ADD_USER,
currentUser.getAccountId(),
user1,
user2);
assertSubgroupAuditEvents(
auditEvents.get(1),
auditEvents.get(0),
Type.ADD_GROUP,
currentUser.getAccountId(),
toGroupInfo(group2),
toGroupInfo(group3));
}
@Test
public void logFormatWithSystemGroups() throws Exception {
AccountGroup group = createInReviewDb("group");
try (TempClockStep step = TestTimeUtil.freezeClock()) {
addSubgroupsInReviewDb(
group.getId(), SystemGroupBackend.ANONYMOUS_USERS, SystemGroupBackend.REGISTERED_USERS);
}
executeSchemaMigration(schema167, group);
GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
ImmutableList<CommitInfo> log = log(group);
assertThat(log).hasSize(3);
// Verify commit that created the group
assertThat(log.get(0)).message().isEqualTo("Create group");
assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
// Verify commit that the group creator as member
assertThat(log.get(1))
.message()
.isEqualTo(
"Update group\n\nAdd: "
+ currentUser.getName()
+ " <"
+ currentUser.getAccountId()
+ "@"
+ serverId
+ ">");
assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
// Verify commit that added system groups
assertThat(log.get(2))
.message()
.isEqualTo(
"Update group\n"
+ "\n"
+ "Add-group: Anonymous Users <global:Anonymous-Users>\n"
+ "Add-group: Registered Users <global:Registered-Users>");
assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
// Verify that audit log is correctly read by Gerrit
List<? extends GroupAuditEventInfo> auditEvents =
gApi.groups().id(group.getGroupUUID().get()).auditLog();
assertThat(auditEvents).hasSize(3);
AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
assertMemberAuditEvent(
auditEvents.get(2), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
assertSubgroupAuditEvents(
auditEvents.get(1),
auditEvents.get(0),
Type.ADD_GROUP,
currentUser.getAccountId(),
groupInfoForExternalGroup(SystemGroupBackend.ANONYMOUS_USERS),
groupInfoForExternalGroup(SystemGroupBackend.REGISTERED_USERS));
}
@Test
public void logFormatWithExternalGroup() throws Exception {
AccountGroup group = createInReviewDb("group");
TestGroupBackend testGroupBackend = new TestGroupBackend();
backends.add("gerrit", testGroupBackend);
AccountGroup.UUID subgroupUuid = testGroupBackend.create("test").getGroupUUID();
assertThat(groupBackend.handles(subgroupUuid)).isTrue();
addSubgroupsInReviewDb(group.getId(), subgroupUuid);
executeSchemaMigration(schema167, group);
GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
ImmutableList<CommitInfo> log = log(group);
assertThat(log).hasSize(3);
// Verify commit that created the group
assertThat(log.get(0)).message().isEqualTo("Create group");
assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
// Verify commit that the group creator as member
assertThat(log.get(1))
.message()
.isEqualTo(
"Update group\n\nAdd: "
+ currentUser.getName()
+ " <"
+ currentUser.getAccountId()
+ "@"
+ serverId
+ ">");
assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
// Verify commit that added system groups
// Note: The schema migration can only resolve names of Gerrit groups, not of external groups
// and system groups, hence the UUID shows up in commit messages where we would otherwise
// expect the group name.
assertThat(log.get(2))
.message()
.isEqualTo(
"Update group\n"
+ "\n"
+ "Add-group: "
+ subgroupUuid.get()
+ " <"
+ subgroupUuid.get()
+ ">");
assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
// Verify that audit log is correctly read by Gerrit
List<? extends GroupAuditEventInfo> auditEvents =
gApi.groups().id(group.getGroupUUID().get()).auditLog();
assertThat(auditEvents).hasSize(2);
AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
assertMemberAuditEvent(
auditEvents.get(1), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
assertSubgroupAuditEvent(
auditEvents.get(0),
Type.ADD_GROUP,
currentUser.getAccountId(),
groupInfoForExternalGroup(subgroupUuid));
}
@Test
public void logFormatWithNonExistingExternalGroup() throws Exception {
AccountGroup group = createInReviewDb("group");
AccountGroup.UUID subgroupUuid = new AccountGroup.UUID("notExisting:foo");
assertThat(groupBackend.handles(subgroupUuid)).isFalse();
addSubgroupsInReviewDb(group.getId(), subgroupUuid);
executeSchemaMigration(schema167, group);
GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
ImmutableList<CommitInfo> log = log(group);
assertThat(log).hasSize(3);
// Verify commit that created the group
assertThat(log.get(0)).message().isEqualTo("Create group");
assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
// Verify commit that the group creator as member
assertThat(log.get(1))
.message()
.isEqualTo(
"Update group\n\nAdd: "
+ currentUser.getName()
+ " <"
+ currentUser.getAccountId()
+ "@"
+ serverId
+ ">");
assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
// Verify commit that added system groups
// Note: The schema migration can only resolve names of Gerrit groups, not of external groups
// and system groups, hence the UUID shows up in commit messages where we would otherwise
// expect the group name.
assertThat(log.get(2))
.message()
.isEqualTo("Update group\n" + "\n" + "Add-group: notExisting:foo <notExisting:foo>");
assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
// Verify that audit log is correctly read by Gerrit
List<? extends GroupAuditEventInfo> auditEvents =
gApi.groups().id(group.getGroupUUID().get()).auditLog();
assertThat(auditEvents).hasSize(2);
AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
assertMemberAuditEvent(
auditEvents.get(1), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
assertSubgroupAuditEvent(
auditEvents.get(0),
Type.ADD_GROUP,
currentUser.getAccountId(),
groupInfoForExternalGroup(subgroupUuid));
}
private static TestGroup.Builder newGroup() {
return TestGroup.builder();
}
private AccountGroup createInReviewDb(String groupName) throws Exception {
AccountGroup group =
new AccountGroup(
new AccountGroup.NameKey(groupName),
new AccountGroup.Id(seq.nextGroupId()),
GroupUUID.make(groupName, serverIdent),
TimeUtil.nowTs());
storeInReviewDb(group);
addMembersInReviewDb(group.getId(), currentUser.getAccountId());
return group;
}
private void storeInReviewDb(GroupInfo... groups) throws Exception {
storeInReviewDb(
Arrays.stream(groups)
.map(Schema_166_to_167_WithGroupsInReviewDbTest::toAccountGroup)
.toArray(AccountGroup[]::new));
}
private void storeInReviewDb(AccountGroup... groups) throws Exception {
try (PreparedStatement stmt =
jdbcSchema
.getConnection()
.prepareStatement(
"INSERT INTO account_groups"
+ " (group_uuid,"
+ " group_id,"
+ " name,"
+ " description,"
+ " created_on,"
+ " owner_group_uuid,"
+ " visible_to_all) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
for (AccountGroup group : groups) {
stmt.setString(1, group.getGroupUUID().get());
stmt.setInt(2, group.getId().get());
stmt.setString(3, group.getName());
stmt.setString(4, group.getDescription());
stmt.setTimestamp(5, group.getCreatedOn());
stmt.setString(6, group.getOwnerGroupUUID().get());
stmt.setString(7, group.isVisibleToAll() ? "Y" : "N");
stmt.addBatch();
}
stmt.executeBatch();
}
}
private void updateInReviewDb(AccountGroup... groups) throws Exception {
try (PreparedStatement stmt =
jdbcSchema
.getConnection()
.prepareStatement(
"UPDATE account_groups SET"
+ " group_uuid = ?,"
+ " name = ?,"
+ " description = ?,"
+ " created_on = ?,"
+ " owner_group_uuid = ?,"
+ " visible_to_all = ?"
+ " WHERE group_id = ?")) {
for (AccountGroup group : groups) {
stmt.setString(1, group.getGroupUUID().get());
stmt.setString(2, group.getName());
stmt.setString(3, group.getDescription());
stmt.setTimestamp(4, group.getCreatedOn());
stmt.setString(5, group.getOwnerGroupUUID().get());
stmt.setString(6, group.isVisibleToAll() ? "Y" : "N");
stmt.setInt(7, group.getId().get());
stmt.addBatch();
}
stmt.executeBatch();
}
}
private AccountGroup getFromReviewDb(AccountGroup.Id groupId) throws Exception {
try (Statement stmt = jdbcSchema.getConnection().createStatement();
ResultSet rs =
stmt.executeQuery(
"SELECT group_uuid,"
+ " name,"
+ " description,"
+ " created_on,"
+ " owner_group_uuid,"
+ " visible_to_all"
+ " FROM account_groups"
+ " WHERE group_id = "
+ groupId.get())) {
if (!rs.next()) {
throw new OrmException(String.format("Group %s not found", groupId.get()));
}
AccountGroup.UUID groupUuid = new AccountGroup.UUID(rs.getString(1));
AccountGroup.NameKey groupName = new AccountGroup.NameKey(rs.getString(2));
String description = rs.getString(3);
Timestamp createdOn = rs.getTimestamp(4);
AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID(rs.getString(5));
boolean visibleToAll = "Y".equals(rs.getString(6));
AccountGroup group = new AccountGroup(groupName, groupId, groupUuid, createdOn);
group.setDescription(description);
group.setOwnerGroupUUID(ownerGroupUuid);
group.setVisibleToAll(visibleToAll);
if (rs.next()) {
throw new OrmException(String.format("Group ID %s is ambiguous", groupId.get()));
}
return group;
}
}
private void addMembersInReviewDb(AccountGroup.Id groupId, Account.Id... memberIds)
throws Exception {
try (PreparedStatement addMemberStmt =
jdbcSchema
.getConnection()
.prepareStatement(
"INSERT INTO account_group_members"
+ " (group_id,"
+ " account_id) VALUES ("
+ groupId.get()
+ ", ?)");
PreparedStatement addMemberAuditStmt =
jdbcSchema
.getConnection()
.prepareStatement(
"INSERT INTO account_group_members_audit"
+ " (group_id,"
+ " account_id,"
+ " added_by,"
+ " added_on) VALUES ("
+ groupId.get()
+ ", ?, "
+ currentUser.getAccountId().get()
+ ", ?)")) {
Timestamp addedOn = TimeUtil.nowTs();
for (Account.Id memberId : memberIds) {
addMemberStmt.setInt(1, memberId.get());
addMemberStmt.addBatch();
addMemberAuditStmt.setInt(1, memberId.get());
addMemberAuditStmt.setTimestamp(2, addedOn);
addMemberAuditStmt.addBatch();
}
addMemberStmt.executeBatch();
addMemberAuditStmt.executeBatch();
}
}
private void addSubgroupsInReviewDb(AccountGroup.Id groupId, AccountGroup.UUID... subgroupUuids)
throws Exception {
try (PreparedStatement addSubGroupStmt =
jdbcSchema
.getConnection()
.prepareStatement(
"INSERT INTO account_group_by_id"
+ " (group_id,"
+ " include_uuid) VALUES ("
+ groupId.get()
+ ", ?)");
PreparedStatement addSubGroupAuditStmt =
jdbcSchema
.getConnection()
.prepareStatement(
"INSERT INTO account_group_by_id_aud"
+ " (group_id,"
+ " include_uuid,"
+ " added_by,"
+ " added_on) VALUES ("
+ groupId.get()
+ ", ?, "
+ currentUser.getAccountId().get()
+ ", ?)")) {
Timestamp addedOn = TimeUtil.nowTs();
for (AccountGroup.UUID subgroupUuid : subgroupUuids) {
addSubGroupStmt.setString(1, subgroupUuid.get());
addSubGroupStmt.addBatch();
addSubGroupAuditStmt.setString(1, subgroupUuid.get());
addSubGroupAuditStmt.setTimestamp(2, addedOn);
addSubGroupAuditStmt.addBatch();
}
addSubGroupStmt.executeBatch();
addSubGroupAuditStmt.executeBatch();
}
}
private AccountInfo createAccount(String name) throws RestApiException {
AccountInput accountInput = new AccountInput();
accountInput.username = name;
accountInput.name = name;
return gApi.accounts().create(accountInput).get();
}
private GroupBundle readGroupBundleFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
return groupBundleFactory.fromNoteDb(allUsersName, allUsersRepo, groupUuid);
}
}
private void executeSchemaMigration(SchemaVersion schema, AccountGroup... groupsToVerify)
throws Exception {
executeSchemaMigration(
schema,
Arrays.stream(groupsToVerify)
.map(AccountGroup::getGroupUUID)
.toArray(AccountGroup.UUID[]::new));
}
private void executeSchemaMigration(SchemaVersion schema, GroupInfo... groupsToVerify)
throws Exception {
executeSchemaMigration(
schema,
Arrays.stream(groupsToVerify)
.map(i -> new AccountGroup.UUID(i.id))
.toArray(AccountGroup.UUID[]::new));
}
private void executeSchemaMigration(SchemaVersion schema, AccountGroup.UUID... groupsToVerify)
throws Exception {
List<GroupBundle> reviewDbBundles = new ArrayList<>();
for (AccountGroup.UUID groupUuid : groupsToVerify) {
reviewDbBundles.add(GroupBundle.Factory.fromReviewDb(db, groupUuid));
}
schema.migrateData(db, new TestUpdateUI());
for (GroupBundle reviewDbBundle : reviewDbBundles) {
assertMigratedCleanly(readGroupBundleFromNoteDb(reviewDbBundle.uuid()), reviewDbBundle);
}
}
private void assertMigratedCleanly(GroupBundle noteDbBundle, GroupBundle expectedReviewDbBundle) {
assertThat(GroupBundle.compareWithAudits(expectedReviewDbBundle, noteDbBundle)).isEmpty();
}
private ImmutableList<CommitInfo> log(AccountGroup group) throws Exception {
ImmutableList.Builder<CommitInfo> result = ImmutableList.builder();
List<Date> commitDates = new ArrayList<>();
try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName);
RevWalk rw = new RevWalk(allUsersRepo)) {
Ref ref = allUsersRepo.exactRef(RefNames.refsGroups(group.getGroupUUID()));
if (ref != null) {
rw.sort(RevSort.REVERSE);
rw.setRetainBody(true);
rw.markStart(rw.parseCommit(ref.getObjectId()));
for (RevCommit c : rw) {
result.add(CommitUtil.toCommitInfo(c));
commitDates.add(c.getCommitterIdent().getWhen());
}
}
}
assertThat(commitDates).named("commit timestamps for %s", result).isOrdered();
return result.build();
}
private ImmutableList<GroupReference> getAllGroupsFromNoteDb()
throws IOException, ConfigInvalidException {
try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
return GroupNameNotes.loadAllGroups(allUsersRepo);
}
}
private Optional<InternalGroup> getGroupFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
return GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid).getLoadedGroup();
}
}
private static OptionalSubject<InternalGroupSubject, InternalGroup> assertThatGroup(
Optional<InternalGroup> group) {
return assertThat(group, InternalGroupSubject::assertThat).named("group");
}
private void assertMemberAuditEvent(
GroupAuditEventInfo info,
Type expectedType,
Account.Id expectedUser,
AccountInfo expectedMember) {
assertThat(info.user._accountId).isEqualTo(expectedUser.get());
assertThat(info.type).isEqualTo(expectedType);
assertThat(info).isInstanceOf(UserMemberAuditEventInfo.class);
assertAccount(((UserMemberAuditEventInfo) info).member, expectedMember);
}
private void assertMemberAuditEvents(
GroupAuditEventInfo info1,
GroupAuditEventInfo info2,
Type expectedType,
Account.Id expectedUser,
AccountInfo expectedMember1,
AccountInfo expectedMember2) {
assertThat(info1).isInstanceOf(UserMemberAuditEventInfo.class);
assertThat(info2).isInstanceOf(UserMemberAuditEventInfo.class);
UserMemberAuditEventInfo event1 = (UserMemberAuditEventInfo) info1;
UserMemberAuditEventInfo event2 = (UserMemberAuditEventInfo) info2;
assertThat(event1.member._accountId)
.isAnyOf(expectedMember1._accountId, expectedMember2._accountId);
assertThat(event2.member._accountId)
.isAnyOf(expectedMember1._accountId, expectedMember2._accountId);
assertThat(event1.member._accountId).isNotEqualTo(event2.member._accountId);
if (event1.member._accountId == expectedMember1._accountId) {
assertMemberAuditEvent(info1, expectedType, expectedUser, expectedMember1);
assertMemberAuditEvent(info2, expectedType, expectedUser, expectedMember2);
} else {
assertMemberAuditEvent(info1, expectedType, expectedUser, expectedMember2);
assertMemberAuditEvent(info2, expectedType, expectedUser, expectedMember1);
}
}
private void assertSubgroupAuditEvent(
GroupAuditEventInfo info,
Type expectedType,
Account.Id expectedUser,
GroupInfo expectedSubGroup) {
assertThat(info.user._accountId).isEqualTo(expectedUser.get());
assertThat(info.type).isEqualTo(expectedType);
assertThat(info).isInstanceOf(GroupMemberAuditEventInfo.class);
assertGroup(((GroupMemberAuditEventInfo) info).member, expectedSubGroup);
}
private void assertSubgroupAuditEvents(
GroupAuditEventInfo info1,
GroupAuditEventInfo info2,
Type expectedType,
Account.Id expectedUser,
GroupInfo expectedSubGroup1,
GroupInfo expectedSubGroup2) {
assertThat(info1).isInstanceOf(GroupMemberAuditEventInfo.class);
assertThat(info2).isInstanceOf(GroupMemberAuditEventInfo.class);
GroupMemberAuditEventInfo event1 = (GroupMemberAuditEventInfo) info1;
GroupMemberAuditEventInfo event2 = (GroupMemberAuditEventInfo) info2;
assertThat(event1.member.id).isAnyOf(expectedSubGroup1.id, expectedSubGroup2.id);
assertThat(event2.member.id).isAnyOf(expectedSubGroup1.id, expectedSubGroup2.id);
assertThat(event1.member.id).isNotEqualTo(event2.member.id);
if (event1.member.id.equals(expectedSubGroup1.id)) {
assertSubgroupAuditEvent(info1, expectedType, expectedUser, expectedSubGroup1);
assertSubgroupAuditEvent(info2, expectedType, expectedUser, expectedSubGroup2);
} else {
assertSubgroupAuditEvent(info1, expectedType, expectedUser, expectedSubGroup2);
assertSubgroupAuditEvent(info2, expectedType, expectedUser, expectedSubGroup1);
}
}
private void assertAccount(AccountInfo actual, AccountInfo expected) {
assertThat(actual._accountId).isEqualTo(expected._accountId);
assertThat(actual.name).isEqualTo(expected.name);
assertThat(actual.email).isEqualTo(expected.email);
assertThat(actual.username).isEqualTo(expected.username);
}
private void assertGroup(GroupInfo actual, GroupInfo expected) {
assertThat(actual.id).isEqualTo(expected.id);
assertThat(actual.name).isEqualTo(expected.name);
assertThat(actual.groupId).isEqualTo(expected.groupId);
}
private GroupInfo groupInfoForExternalGroup(AccountGroup.UUID groupUuid) {
GroupInfo groupInfo = new GroupInfo();
groupInfo.id = IdString.fromDecoded(groupUuid.get()).encoded();
if (groupBackend.handles(groupUuid)) {
groupInfo.name = groupBackend.get(groupUuid).getName();
}
return groupInfo;
}
private static AccountGroup toAccountGroup(GroupInfo info) {
AccountGroup group =
new AccountGroup(
new AccountGroup.NameKey(info.name),
new AccountGroup.Id(info.groupId),
new AccountGroup.UUID(info.id),
info.createdOn);
group.setDescription(info.description);
if (info.ownerId != null) {
group.setOwnerGroupUUID(new AccountGroup.UUID(info.ownerId));
}
group.setVisibleToAll(
info.options != null && info.options.visibleToAll != null && info.options.visibleToAll);
return group;
}
private static GroupInfo toGroupInfo(AccountGroup group) {
GroupInfo groupInfo = new GroupInfo();
groupInfo.id = group.getGroupUUID().get();
groupInfo.groupId = group.getId().get();
groupInfo.name = group.getName();
groupInfo.createdOn = group.getCreatedOn();
groupInfo.description = group.getDescription();
groupInfo.owner = group.getOwnerGroupUUID().get();
groupInfo.options = new GroupOptionsInfo();
groupInfo.options.visibleToAll = group.isVisibleToAll() ? true : null;
return groupInfo;
}
}