| // Copyright (C) 2020 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.plugins.codeowners.acceptance.api; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth8.assertThat; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability; |
| import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigSubject.assertThat; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.Iterables; |
| import com.google.common.util.concurrent.AtomicLongMap; |
| import com.google.gerrit.acceptance.ExtensionRegistry; |
| import com.google.gerrit.acceptance.ExtensionRegistry.Registration; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.account.AccountOperations; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.common.data.GlobalCapability; |
| import com.google.gerrit.entities.BranchNameKey; |
| import com.google.gerrit.entities.Permission; |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; |
| import com.google.gerrit.extensions.restapi.AuthException; |
| import com.google.gerrit.extensions.restapi.BadRequestException; |
| import com.google.gerrit.extensions.restapi.RestApiException; |
| import com.google.gerrit.extensions.restapi.UnprocessableEntityException; |
| import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT; |
| import com.google.gerrit.plugins.codeowners.api.RenameEmailInput; |
| import com.google.gerrit.plugins.codeowners.api.RenameEmailResultInfo; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigFileUpdateScanner; |
| import com.google.gerrit.plugins.codeowners.restapi.RenameEmail; |
| import com.google.inject.Inject; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Optional; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectLoader; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevTree; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.treewalk.TreeWalk; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * Acceptance test for the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST |
| * endpoint. |
| * |
| * <p>Further tests for the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST |
| * endpoint that require using the REST API are implemented in {@link |
| * com.google.gerrit.plugins.codeowners.acceptance.restapi.RenameEmailRestIT}. |
| */ |
| public class RenameEmailIT extends AbstractCodeOwnersIT { |
| @Inject private AccountOperations accountOperations; |
| @Inject private ProjectOperations projectOperations; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| @Inject private ExtensionRegistry extensionRegistry; |
| |
| private CodeOwnerConfigFileUpdateScanner codeOwnerConfigFileUpdateScanner; |
| |
| @Before |
| public void setup() throws Exception { |
| codeOwnerConfigFileUpdateScanner = |
| plugin.getSysInjector().getInstance(CodeOwnerConfigFileUpdateScanner.class); |
| } |
| |
| @Test |
| public void oldEmailIsRequired() throws Exception { |
| RenameEmailInput input = new RenameEmailInput(); |
| input.newEmail = "new@example.com"; |
| BadRequestException exception = |
| assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input)); |
| assertThat(exception).hasMessageThat().isEqualTo("old email is required"); |
| } |
| |
| @Test |
| public void newEmailIsRequired() throws Exception { |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = "old@example.com"; |
| BadRequestException exception = |
| assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input)); |
| assertThat(exception).hasMessageThat().isEqualTo("new email is required"); |
| } |
| |
| @Test |
| public void oldEmailNotResolvable() throws Exception { |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = "unknown@example.com"; |
| input.newEmail = admin.email(); |
| UnprocessableEntityException exception = |
| assertThrows( |
| UnprocessableEntityException.class, () -> renameEmail(project, "master", input)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("cannot resolve email %s", input.oldEmail)); |
| } |
| |
| @Test |
| public void newEmailNotResolvable() throws Exception { |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = admin.email(); |
| input.newEmail = "unknown@example.com"; |
| UnprocessableEntityException exception = |
| assertThrows( |
| UnprocessableEntityException.class, () -> renameEmail(project, "master", input)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("cannot resolve email %s", input.newEmail)); |
| } |
| |
| @Test |
| public void emailsMustBelongToSameAccount() throws Exception { |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = admin.email(); |
| input.newEmail = user.email(); |
| BadRequestException exception = |
| assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo( |
| String.format( |
| "emails must belong to the same account" |
| + " (old email %s is owned by account %d, new email %s is owned by account %d)", |
| admin.email(), admin.id().get(), user.email(), user.id().get())); |
| } |
| |
| @Test |
| public void oldAndNewEmailMustDiffer() throws Exception { |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = admin.email(); |
| input.newEmail = admin.email(); |
| BadRequestException exception = |
| assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input)); |
| assertThat(exception).hasMessageThat().isEqualTo("old and new email must differ"); |
| } |
| |
| @Test |
| public void requiresAuthenticatedUser() throws Exception { |
| requestScopeOperations.setApiUserAnonymous(); |
| AuthException authException = |
| assertThrows( |
| AuthException.class, () -> renameEmail(project, "master", new RenameEmailInput())); |
| assertThat(authException).hasMessageThat().contains("Authentication required"); |
| } |
| |
| @Test |
| public void renameEmailRequiresDirectPushPermissionsForNonProjectOwner() throws Exception { |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| AuthException exception = |
| assertThrows(AuthException.class, () -> renameEmail(project, "master", input)); |
| assertThat(exception).hasMessageThat().isEqualTo("not permitted: update on refs/heads/master"); |
| } |
| |
| @Test |
| public void renameEmail_noCodeOwnerConfig() throws Exception { |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNull(); |
| } |
| |
| @Test |
| public void renameEmail_noUpdateIfEmailIsNotContainedInCodeOwnerConfigs() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNull(); |
| } |
| |
| @Test |
| public void renameOwnEmailWithDirectPushPermission() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey1 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .addCodeOwnerEmail(user.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey2 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(user.email()) |
| .create(); |
| |
| // grant all users direct push permissions |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS)) |
| .update(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, admin.email()); |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail); |
| } |
| |
| @Test |
| public void renameOtherEmailWithDirectPushPermission() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey1 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .addCodeOwnerEmail(user.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey2 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| // grant all users direct push permissions |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS)) |
| .update(); |
| |
| // Allow all users to see secondary emails. |
| projectOperations |
| .project(allProjects) |
| .forUpdate() |
| .add(allowCapability(GlobalCapability.VIEW_SECONDARY_EMAILS).group(REGISTERED_USERS)) |
| .update(); |
| |
| String secondaryEmail = "admin-foo@example.com"; |
| accountOperations.account(admin.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = admin.email(); |
| input.newEmail = secondaryEmail; |
| RefUpdateCounter refUpdateCounter = new RefUpdateCounter(); |
| try (Registration registration = extensionRegistry.newRegistration().add(refUpdateCounter)) { |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| } |
| refUpdateCounter.assertRefUpdateFor(RefUpdateCounter.projectRef(project, "refs/heads/master")); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, user.email()); |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail); |
| } |
| |
| @Test |
| public void renameOwnEmailAsProjectOwner() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey1 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey2 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| String secondaryEmail = "admin-foo@example.com"; |
| accountOperations.account(admin.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = admin.email(); |
| input.newEmail = secondaryEmail; |
| RefUpdateCounter refUpdateCounter = new RefUpdateCounter(); |
| try (Registration registration = extensionRegistry.newRegistration().add(refUpdateCounter)) { |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| } |
| refUpdateCounter.assertRefUpdateFor(RefUpdateCounter.projectRef(project, "refs/heads/master")); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, user.email()); |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail); |
| } |
| |
| @Test |
| public void renameOtherEmailAsProjectOwner() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey1 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey2 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(user.email()) |
| .create(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, admin.email()); |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail); |
| } |
| |
| @Test |
| public void renameEmail_callingUserBecomesCommitAuthor() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| assertThat(result.commit.author.email).isEqualTo(admin.email()); |
| } |
| |
| @Test |
| public void renameEmailWithDefaultCommitMessage() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| assertThat(result.commit.message).isEqualTo(RenameEmail.DEFAULT_COMMIT_MESSAGE); |
| } |
| |
| @Test |
| public void renameEmailWithSpecifiedCommitMessage() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| input.message = "Update email with custom message"; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| assertThat(result.commit.message).isEqualTo(input.message); |
| } |
| |
| @Test |
| public void renameEmail_specifiedCommitMessageIsTrimmed() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| String message = "Update email with custom message"; |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| input.message = " " + message + "\t"; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| assertThat(result.commit.message).isEqualTo(message); |
| } |
| |
| @Test |
| public void renameEmail_lineCommentsArePreserved() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| // insert some comments |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), |
| "Insert comments", |
| (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> { |
| StringBuilder b = new StringBuilder(); |
| // insert comment line at the top of the file |
| b.append("# top comment\n"); |
| |
| Iterable<String> lines = Splitter.on('\n').split(codeOwnerConfigFileContent); |
| b.append(Iterables.get(lines, /* position= */ 0) + "\n"); |
| |
| // insert comment line in the middle of the file |
| b.append("# middle comment\n"); |
| |
| for (String line : Iterables.skip(lines, /* numberToSkip= */ 1)) { |
| b.append(line + "\n"); |
| } |
| |
| // insert comment line at the bottom of the file |
| b.append("# bottom comment\n"); |
| |
| return Optional.of(b.toString()); |
| }); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| renameEmail(project, "master", input); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, admin.email()); |
| |
| // verify that the comments are still present |
| String codeOwnerConfigFileContent = |
| codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent(); |
| Iterable<String> lines = Splitter.on('\n').split(codeOwnerConfigFileContent); |
| assertThat(Iterables.get(lines, /* position= */ 0)).isEqualTo("# top comment"); |
| assertThat(Iterables.get(lines, /* position= */ 2)).isEqualTo("# middle comment"); |
| assertThat(Iterables.get(lines, /* position= */ Iterables.size(lines) - 2)) |
| .isEqualTo("# bottom comment"); |
| } |
| |
| @Test |
| public void renameEmail_inlineCommentsArePreserved() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| // insert some inline comments |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), |
| "Insert comments", |
| (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> { |
| StringBuilder b = new StringBuilder(); |
| for (String line : Splitter.on('\n').split(codeOwnerConfigFileContent)) { |
| if (line.contains(user.email())) { |
| b.append(line + "# some comment\n"); |
| continue; |
| } |
| if (line.contains(admin.email())) { |
| b.append(line + "# other comment\n"); |
| continue; |
| } |
| b.append(line + "\n"); |
| } |
| |
| return Optional.of(b.toString()); |
| }); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| renameEmail(project, "master", input); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, admin.email()); |
| |
| // verify that the inline comments are still present |
| String codeOwnerConfigFileContent = |
| codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent(); |
| for (String line : Splitter.on('\n').split(codeOwnerConfigFileContent)) { |
| if (line.contains(secondaryEmail)) { |
| assertThat(line).endsWith("# some comment"); |
| } else if (line.contains(admin.email())) { |
| assertThat(line).endsWith("# other comment"); |
| } |
| } |
| } |
| |
| @Test |
| public void renameEmail_emailInCommentIsReplaced() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| // insert some comments |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), |
| "Insert comments", |
| (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> |
| Optional.of("# foo " + user.email() + " bar\n" + codeOwnerConfigFileContent)); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| renameEmail(project, "master", input); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, admin.email()); |
| |
| // verify that the comments are still present |
| String codeOwnerConfigFileContent = |
| codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent(); |
| assertThat( |
| Iterables.get(Splitter.on('\n').split(codeOwnerConfigFileContent), /* position= */ 0)) |
| .endsWith("# foo " + secondaryEmail + " bar"); |
| } |
| |
| @Test |
| public void renameEmail_emailThatContainsEmailToBeReplacedAsSubstringStaysIntact() |
| throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| TestAccount otherUser1 = |
| accountCreator.create( |
| "otherUser1", "foo" + user.email(), "Other User 1", /* displayName= */ null); |
| TestAccount otherUser2 = |
| accountCreator.create( |
| "otherUser2", user.email() + "bar", "Other User 2", /* displayName= */ null); |
| TestAccount otherUser3 = |
| accountCreator.create( |
| "otherUser3", "foo" + user.email() + "bar", "Other User 3", /* displayName= */ null); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(user.email()) |
| .addCodeOwnerEmail(otherUser1.email()) |
| .addCodeOwnerEmail(otherUser2.email()) |
| .addCodeOwnerEmail(otherUser3.email()) |
| .create(); |
| |
| // grant all users direct push permissions |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS)) |
| .update(); |
| |
| String secondaryEmail = "user-new@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly( |
| secondaryEmail, otherUser1.email(), otherUser2.email(), otherUser3.email()); |
| } |
| |
| @Test |
| public void renameEmailDoesNotTouchCodeOwnerConfigsThatDoNotContainTheEmail() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| TestAccount user2 = accountCreator.user2(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey1 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .addCodeOwnerEmail(user.email()) |
| .create(); |
| |
| // Create a code owner config that doesn't contain the email to be replaced. |
| CodeOwnerConfig.Key codeOwnerConfigKey2 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(user2.email()) |
| .create(); |
| |
| // grant all users direct push permissions |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS)) |
| .update(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, admin.email()); |
| |
| // Check that the second code owner config is still intact. |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(user2.email()); |
| } |
| |
| @Test |
| public void renameEmailDoesNotTouchNonCodeOwnerConfigFiles() throws Exception { |
| skipTestIfRenameEmailNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .addCodeOwnerEmail(user.email()) |
| .create(); |
| |
| // Create non code owner config files. |
| String contentFileA = "some content"; |
| String contentFileB = |
| String.format( |
| "some content that contains the email %s that is being renamed", user.email()); |
| try (TestRepository<Repository> testRepo = |
| new TestRepository<>(repoManager.openRepository(project))) { |
| testRepo.update( |
| "master", |
| testRepo |
| .commit() |
| .message("Update project.config from test") |
| .parent(projectOperations.project(project).getHead("master")) |
| .add("A", contentFileA) |
| .add("B", contentFileB)); |
| } |
| |
| // grant all users direct push permissions |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS)) |
| .update(); |
| |
| String secondaryEmail = "user-foo@example.com"; |
| accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| RenameEmailInput input = new RenameEmailInput(); |
| input.oldEmail = user.email(); |
| input.newEmail = secondaryEmail; |
| RenameEmailResultInfo result = renameEmail(project, "master", input); |
| assertThat(result.commit).isNotNull(); |
| |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get()) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(secondaryEmail, admin.email()); |
| |
| // Check that the non code owner config files are still intact. |
| assertThat(getFileContent(project, "master", "A")).hasValue(contentFileA); |
| assertThat(getFileContent(project, "master", "B")).hasValue(contentFileB); |
| } |
| |
| private RenameEmailResultInfo renameEmail( |
| Project.NameKey projectName, String branchName, RenameEmailInput input) |
| throws RestApiException { |
| return projectCodeOwnersApiFactory |
| .project(projectName) |
| .branch(branchName) |
| .renameEmailInCodeOwnerConfigFiles(input); |
| } |
| |
| private void skipTestIfRenameEmailNotSupportedByCodeOwnersBackend() { |
| // the proto backend doesn't support renaming emails |
| assumeThatCodeOwnersBackendIsNotProtoBackend(); |
| } |
| |
| private Optional<String> getFileContent(Project.NameKey project, String branch, String fileName) { |
| try (Repository repo = repoManager.openRepository(project); |
| RevWalk rw = new RevWalk(repo)) { |
| if (!branch.startsWith(Constants.R_REFS)) { |
| branch = Constants.R_HEADS + branch; |
| } |
| Ref ref = repo.exactRef(branch); |
| if (ref == null) { |
| return Optional.empty(); |
| } |
| RevTree tree = rw.parseTree(ref.getObjectId()); |
| TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), fileName, tree); |
| if (tw == null) { |
| return Optional.empty(); |
| } |
| ObjectLoader loader = rw.getObjectReader().open(tw.getObjectId(0)); |
| String fileContent = new String(loader.getCachedBytes(), UTF_8); |
| return Optional.of(fileContent); |
| } catch (Exception e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| private static class RefUpdateCounter implements GitReferenceUpdatedListener { |
| private final AtomicLongMap<String> countsByProjectRefs = AtomicLongMap.create(); |
| |
| static String projectRef(Project.NameKey project, String ref) { |
| return projectRef(project.get(), ref); |
| } |
| |
| static String projectRef(String project, String ref) { |
| return project + ":" + ref; |
| } |
| |
| @Override |
| public void onGitReferenceUpdated(Event event) { |
| countsByProjectRefs.incrementAndGet(projectRef(event.getProjectName(), event.getRefName())); |
| } |
| |
| void clear() { |
| countsByProjectRefs.clear(); |
| } |
| |
| void assertRefUpdateFor(String... projectRefs) { |
| Map<String, Long> expectedRefUpdateCounts = new HashMap<>(); |
| for (String projectRef : projectRefs) { |
| expectedRefUpdateCounts.put(projectRef, 1L); |
| } |
| assertRefUpdateFor(expectedRefUpdateCounts); |
| } |
| |
| void assertRefUpdateFor(Map<String, Long> expectedProjectRefUpdateCounts) { |
| assertThat(countsByProjectRefs.asMap()) |
| .containsExactlyEntriesIn(expectedProjectRefUpdateCounts); |
| clear(); |
| } |
| } |
| } |