| // 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.backend; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth8.assertThat; |
| import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigSubject.assertThatOptional; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| |
| import com.google.gerrit.acceptance.config.GerritConfig; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.exceptions.StorageException; |
| import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest; |
| import com.google.gerrit.plugins.codeowners.testing.backend.TestCodeOwnerConfigStorage; |
| import com.google.gerrit.server.IdentifiedUser; |
| import java.nio.file.Paths; |
| import java.util.Optional; |
| import org.eclipse.jgit.errors.MissingObjectException; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * Base class for testing {@link AbstractFileBasedCodeOwnerBackend}s. |
| * |
| * <p>Implements the tests that are common for all backends once instead of once per backend. |
| */ |
| public abstract class AbstractFileBasedCodeOwnerBackendTest extends AbstractCodeOwnersTest { |
| protected TestCodeOwnerConfigStorage testCodeOwnerConfigStorage; |
| protected CodeOwnerBackend codeOwnerBackend; |
| |
| @Before |
| public void setUpCodeOwnersPlugin() throws Exception { |
| testCodeOwnerConfigStorage = |
| plugin |
| .getSysInjector() |
| .getInstance(TestCodeOwnerConfigStorage.Factory.class) |
| .create(getFileName(), plugin.getSysInjector().getInstance(getParserClass())); |
| codeOwnerBackend = plugin.getSysInjector().getInstance(getBackendClass()); |
| } |
| |
| /** |
| * Must return the class of the {@link AbstractFileBasedCodeOwnerBackend} that should be tested. |
| */ |
| protected abstract Class<? extends AbstractFileBasedCodeOwnerBackend> getBackendClass(); |
| |
| /** Must return the name of the files in which {@link CodeOwnerConfig}s are stored. */ |
| protected abstract String getFileName(); |
| |
| /** |
| * Must return the class of the {@link CodeOwnerConfigParser} that is used by the code owners |
| * backend. |
| */ |
| protected abstract Class<? extends CodeOwnerConfigParser> getParserClass(); |
| |
| @Test |
| public void getNonExistingCodeOwnerConfig() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| CodeOwnerConfig.Key.create(project, "master", "/non-existing/"); |
| assertThatOptional(codeOwnerBackend.getCodeOwnerConfig(codeOwnerConfigKey, null)).isEmpty(); |
| } |
| |
| @Test |
| public void getCodeOwnerConfigFromNonExistingBranch() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| CodeOwnerConfig.Key.create(project, "non-existing", "/"); |
| assertThatOptional(codeOwnerBackend.getCodeOwnerConfig(codeOwnerConfigKey, null)).isEmpty(); |
| } |
| |
| @Test |
| public void getCodeOwnerConfig() throws Exception { |
| testGetCodeOwnerConfig(getFileName()); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fileExtension", value = "foo") |
| public void getCodeOwnerConfigWithFileExtension() throws Exception { |
| testGetCodeOwnerConfig(getFileName() + ".foo"); |
| } |
| |
| @Test |
| public void getCodeOwnerConfigWithPostFix() throws Exception { |
| testGetCodeOwnerConfig(getFileName() + "_post_fix"); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fileExtension", value = "foo") |
| public void getCodeOwnerConfigWithPostFixAndFileExtension() throws Exception { |
| testGetCodeOwnerConfig(getFileName() + "_post_fix.foo"); |
| } |
| |
| @Test |
| public void getCodeOwnerConfigWithPreFix() throws Exception { |
| testGetCodeOwnerConfig("pre_fix_" + getFileName()); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fileExtension", value = "foo") |
| public void getCodeOwnerConfigWithPreFixAndFileExtension() throws Exception { |
| testGetCodeOwnerConfig("pre_fix_" + getFileName() + ".foo"); |
| } |
| |
| private void testGetCodeOwnerConfig(String fileName) throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| CodeOwnerConfig.Key.create(project, "master", "/", fileName); |
| CodeOwnerConfig codeOwnerConfigInRepository = |
| testCodeOwnerConfigStorage.writeCodeOwnerConfig( |
| codeOwnerConfigKey, |
| b -> b.addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(admin.email()))); |
| |
| Optional<CodeOwnerConfig> codeOwnerConfig = |
| codeOwnerBackend.getCodeOwnerConfig(codeOwnerConfigKey, null); |
| assertThatOptional(codeOwnerConfig).value().isEqualTo(codeOwnerConfigInRepository); |
| } |
| |
| @Test |
| public void getCodeOwnerConfig_noCodeOwnerConfigFoundForUnsupportedFileName() throws Exception { |
| testGetCodeOwnerConfig_noCodeOwnerConfigFound("UNSUPPORTED_CODE_OWNERS"); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fileExtension", value = "foo") |
| public void getCodeOwnerConfig_noCodeOwnerConfigFoundWhenUsingWrongFileExtension() |
| throws Exception { |
| testGetCodeOwnerConfig_noCodeOwnerConfigFound(getFileName() + ".bar"); |
| } |
| |
| @Test |
| public void getCodeOwnerConfig_noCodeOwnerConfigFoundWhenUsingFileExtensionButNoneWasConfigured() |
| throws Exception { |
| testGetCodeOwnerConfig_noCodeOwnerConfigFound(getFileName() + ".foo"); |
| } |
| |
| @Test |
| public void getCodeOwnerConfig_noCodeOwnerConfigFoundWhenUsingPreAndPostFix() throws Exception { |
| testGetCodeOwnerConfig_noCodeOwnerConfigFound("pre_fix_" + getFileName() + "_post_fix"); |
| } |
| |
| @Test |
| public void getCodeOwnerConfig_noCodeOwnerConfigFoundWhenUsingInvalidPostFix() throws Exception { |
| testGetCodeOwnerConfig_noCodeOwnerConfigFound(getFileName() + "_i n v a l i d"); |
| } |
| |
| @Test |
| public void getCodeOwnerConfig_noCodeOwnerConfigFoundWhenUsingInvalidPreFix() throws Exception { |
| testGetCodeOwnerConfig_noCodeOwnerConfigFound("i n v a l i d_" + getFileName()); |
| } |
| |
| private void testGetCodeOwnerConfig_noCodeOwnerConfigFound(String fileName) throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| CodeOwnerConfig.Key.create(project, "master", "/", fileName); |
| assertThatOptional(codeOwnerBackend.getCodeOwnerConfig(codeOwnerConfigKey, null)).isEmpty(); |
| } |
| |
| @Test |
| public void getCodeOwnerConfigFromOldRevision() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/"); |
| CodeOwnerConfig codeOwnerConfigInRepository = |
| testCodeOwnerConfigStorage.writeCodeOwnerConfig( |
| codeOwnerConfigKey, |
| b -> b.addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(admin.email()))); |
| ObjectId revision1 = codeOwnerConfigInRepository.revision(); |
| |
| // update the code owner config, which creates a new revision |
| ObjectId revision2 = |
| testCodeOwnerConfigStorage |
| .writeCodeOwnerConfig(codeOwnerConfigKey, b -> b.setIgnoreParentCodeOwners()) |
| .revision(); |
| assertThat(revision1).isNotEqualTo(revision2); |
| |
| Optional<CodeOwnerConfig> codeOwnerConfig = |
| codeOwnerBackend.getCodeOwnerConfig(codeOwnerConfigKey, revision1); |
| assertThatOptional(codeOwnerConfig).value().isEqualTo(codeOwnerConfigInRepository); |
| } |
| |
| @Test |
| public void cannotGetCodeOwnerConfigFromNonExistingRevision() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/"); |
| StorageException exception = |
| assertThrows( |
| StorageException.class, |
| () -> |
| codeOwnerBackend.getCodeOwnerConfig( |
| codeOwnerConfigKey, |
| ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("failed to load code owner config %s", codeOwnerConfigKey)); |
| assertThat(exception).hasCauseThat().isInstanceOf(MissingObjectException.class); |
| } |
| |
| @Test |
| public void createCodeOwnerConfigInitiatedByServer() throws Exception { |
| testCreateCodeOwnerConfig(null); |
| } |
| |
| @Test |
| public void createCodeOwnerConfigInitiatedByUser() throws Exception { |
| testCreateCodeOwnerConfig(identifiedUserFactory.create(user.id())); |
| } |
| |
| private void testCreateCodeOwnerConfig(@Nullable IdentifiedUser currentUser) throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/"); |
| |
| try (Repository repo = repoManager.openRepository(project)) { |
| // Remember head for later assertions. |
| RevCommit origHead = getHead(repo, codeOwnerConfigKey.ref()); |
| |
| // Create the code owner config. |
| Optional<CodeOwnerConfig> codeOwnerConfig = |
| codeOwnerBackend.upsertCodeOwnerConfig( |
| codeOwnerConfigKey, |
| CodeOwnerConfigUpdate.builder() |
| .setCodeOwnerSetsModification( |
| CodeOwnerSetModification.set( |
| CodeOwnerSet.createWithoutPathExpressions(admin.email()))) |
| .build(), |
| currentUser); |
| assertThatOptional(codeOwnerConfig) |
| .value() |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(admin.email()); |
| |
| // Check the metadata of the created commit. |
| RevCommit newHead = getHead(repo, codeOwnerConfigKey.ref()); |
| assertThat(newHead.getParent(0)).isEqualTo(origHead); |
| assertThat(newHead.getShortMessage()).isEqualTo("Create code owner config"); |
| |
| // Check author identity. |
| if (currentUser != null) { |
| assertThat(newHead.getAuthorIdent().getEmailAddress()) |
| .isEqualTo(currentUser.getAccount().preferredEmail()); |
| } else { |
| assertThat(newHead.getAuthorIdent().getEmailAddress()) |
| .isEqualTo(serverIdent.get().getEmailAddress()); |
| assertThat(newHead.getCommitterIdent()).isEqualTo(newHead.getAuthorIdent()); |
| } |
| |
| // Check committer identity. |
| assertThat(newHead.getCommitterIdent().getEmailAddress()) |
| .isEqualTo(serverIdent.get().getEmailAddress()); |
| assertThat(newHead.getCommitterIdent().getTimeZone()) |
| .isEqualTo(newHead.getAuthorIdent().getTimeZone()); |
| assertThat(newHead.getCommitterIdent().getWhen()) |
| .isEqualTo(newHead.getAuthorIdent().getWhen()); |
| } |
| } |
| |
| @Test |
| public void updateCodeOwnerConfigInitiatedByServer() throws Exception { |
| testUpdateCodeOwnerConfig(null); |
| } |
| |
| @Test |
| public void updateCodeOwnerConfigInitiatedByUser() throws Exception { |
| testUpdateCodeOwnerConfig(identifiedUserFactory.create(user.id())); |
| } |
| |
| private void testUpdateCodeOwnerConfig(@Nullable IdentifiedUser currentUser) throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/"); |
| testCodeOwnerConfigStorage.writeCodeOwnerConfig( |
| codeOwnerConfigKey, |
| b -> b.addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(admin.email()))); |
| |
| try (Repository repo = repoManager.openRepository(project)) { |
| // Remember head for later assertions. |
| RevCommit origHead = getHead(repo, codeOwnerConfigKey.ref()); |
| |
| // Update the code owner config. |
| Optional<CodeOwnerConfig> codeOwnerConfig = |
| codeOwnerBackend.upsertCodeOwnerConfig( |
| codeOwnerConfigKey, |
| CodeOwnerConfigUpdate.builder() |
| .setCodeOwnerSetsModification(CodeOwnerSetModification.addToOnlySet(user.email())) |
| .build(), |
| currentUser); |
| assertThatOptional(codeOwnerConfig) |
| .value() |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(admin.email(), user.email()); |
| |
| // Check the metadata of the created commit. |
| RevCommit newHead = getHead(repo, codeOwnerConfigKey.ref()); |
| assertThat(newHead.getParent(0)).isEqualTo(origHead); |
| assertThat(newHead.getShortMessage()).isEqualTo("Update code owner config"); |
| |
| // Check author identity. |
| if (currentUser != null) { |
| assertThat(newHead.getAuthorIdent().getEmailAddress()) |
| .isEqualTo(currentUser.getAccount().preferredEmail()); |
| } else { |
| assertThat(newHead.getAuthorIdent().getEmailAddress()) |
| .isEqualTo(serverIdent.get().getEmailAddress()); |
| assertThat(newHead.getCommitterIdent()).isEqualTo(newHead.getAuthorIdent()); |
| } |
| |
| // Check committer identity. |
| assertThat(newHead.getCommitterIdent().getEmailAddress()) |
| .isEqualTo(serverIdent.get().getEmailAddress()); |
| assertThat(newHead.getCommitterIdent().getTimeZone()) |
| .isEqualTo(newHead.getAuthorIdent().getTimeZone()); |
| assertThat(newHead.getCommitterIdent().getWhen()) |
| .isEqualTo(newHead.getAuthorIdent().getWhen()); |
| } |
| } |
| |
| @Test |
| public void deleteCodeOwnerConfigInitiatedByServer() throws Exception { |
| testDeleteCodeOwnerConfigInitiatedByServer(null); |
| } |
| |
| @Test |
| public void deleteCodeOwnerConfigInitiatedByUser() throws Exception { |
| testDeleteCodeOwnerConfigInitiatedByServer(identifiedUserFactory.create(user.id())); |
| } |
| |
| private void testDeleteCodeOwnerConfigInitiatedByServer(@Nullable IdentifiedUser currentUser) |
| throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/"); |
| testCodeOwnerConfigStorage.writeCodeOwnerConfig( |
| codeOwnerConfigKey, |
| b -> b.addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(admin.email()))); |
| |
| try (Repository repo = repoManager.openRepository(project)) { |
| // Remember head for later assertions. |
| RevCommit origHead = getHead(repo, codeOwnerConfigKey.ref()); |
| |
| // Update the code owner config. |
| Optional<CodeOwnerConfig> codeOwnerConfig = |
| codeOwnerBackend.upsertCodeOwnerConfig( |
| codeOwnerConfigKey, |
| CodeOwnerConfigUpdate.builder() |
| .setCodeOwnerSetsModification(CodeOwnerSetModification.clear()) |
| .build(), |
| currentUser); |
| assertThatOptional(codeOwnerConfig).isEmpty(); |
| |
| // Check the metadata of the created commit. |
| RevCommit newHead = getHead(repo, codeOwnerConfigKey.ref()); |
| assertThat(newHead.getParent(0)).isEqualTo(origHead); |
| assertThat(newHead.getShortMessage()).isEqualTo("Delete code owner config"); |
| |
| // Check author identity. |
| if (currentUser != null) { |
| assertThat(newHead.getAuthorIdent().getEmailAddress()) |
| .isEqualTo(currentUser.getAccount().preferredEmail()); |
| } else { |
| assertThat(newHead.getAuthorIdent().getEmailAddress()) |
| .isEqualTo(serverIdent.get().getEmailAddress()); |
| assertThat(newHead.getCommitterIdent()).isEqualTo(newHead.getAuthorIdent()); |
| } |
| |
| // Check committer identity. |
| assertThat(newHead.getCommitterIdent().getEmailAddress()) |
| .isEqualTo(serverIdent.get().getEmailAddress()); |
| assertThat(newHead.getCommitterIdent().getTimeZone()) |
| .isEqualTo(newHead.getAuthorIdent().getTimeZone()); |
| assertThat(newHead.getCommitterIdent().getWhen()) |
| .isEqualTo(newHead.getAuthorIdent().getWhen()); |
| } |
| } |
| |
| @Test |
| public void noOpCodeOwnerConfigUpdate() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/"); |
| testCodeOwnerConfigStorage.writeCodeOwnerConfig( |
| codeOwnerConfigKey, |
| b -> b.addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(admin.email()))); |
| |
| try (Repository repo = repoManager.openRepository(project)) { |
| // Remember head for later assertions. |
| RevCommit origHead = getHead(repo, codeOwnerConfigKey.ref()); |
| |
| // Update the code owner config. |
| Optional<CodeOwnerConfig> codeOwnerConfig = |
| codeOwnerBackend.upsertCodeOwnerConfig( |
| codeOwnerConfigKey, CodeOwnerConfigUpdate.builder().build(), null); |
| assertThatOptional(codeOwnerConfig) |
| .value() |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(admin.email()); |
| |
| // Check that no commit was created. |
| assertThat(getHead(repo, codeOwnerConfigKey.ref())).isEqualTo(origHead); |
| } |
| } |
| |
| @Test |
| public void cannotUpdateCodeOwnerConfigInNonExistingBranch() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| CodeOwnerConfig.Key.create(project, "non-existing", "/"); |
| |
| try (Repository repo = repoManager.openRepository(project)) { |
| // Update the code owner config. |
| IllegalStateException exception = |
| assertThrows( |
| IllegalStateException.class, |
| () -> |
| codeOwnerBackend.upsertCodeOwnerConfig( |
| codeOwnerConfigKey, CodeOwnerConfigUpdate.builder().build(), null)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("branch %s does not exist", codeOwnerConfigKey.ref())); |
| |
| // Check that the branch was not created. |
| assertThat(repo.exactRef(codeOwnerConfigKey.ref())).isNull(); |
| } |
| } |
| |
| @Test |
| public void getFilePathForCodeOwnerConfigKeyWithoutFileName() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/"); |
| assertThat(codeOwnerBackend.getFilePath(codeOwnerConfigKey)) |
| .isEqualTo(Paths.get(codeOwnerConfigKey.folderPath() + getFileName())); |
| } |
| |
| @Test |
| public void getFilePathForCodeOwnerConfigKeyWithFileName() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| CodeOwnerConfig.Key.create(project, "master", "/", getFileName() + "_foo_bar"); |
| assertThat(codeOwnerBackend.getFilePath(codeOwnerConfigKey)) |
| .isEqualTo( |
| Paths.get(codeOwnerConfigKey.folderPath() + codeOwnerConfigKey.fileName().get())); |
| } |
| } |