| // 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.base.Preconditions.checkNotNull; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.gerrit.server.project.ProjectCache.illegalState; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.entities.BranchNameKey; |
| import com.google.gerrit.entities.RefNames; |
| import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest; |
| import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations; |
| import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestPathExpressions; |
| import com.google.gerrit.plugins.codeowners.backend.config.InvalidPluginConfigurationException; |
| import com.google.gerrit.server.project.ProjectState; |
| import com.google.gerrit.server.restapi.project.DeleteRef; |
| import com.google.inject.Inject; |
| import java.io.IOException; |
| import java.nio.file.Paths; |
| import java.util.function.Consumer; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.junit.MockitoJUnit; |
| import org.mockito.junit.MockitoRule; |
| import org.mockito.quality.Strictness; |
| |
| /** Tests for {@link CodeOwnerConfigHierarchy}. */ |
| public class CodeOwnerConfigHierarchyTest extends AbstractCodeOwnersTest { |
| @Rule public final MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); |
| |
| @Mock private CodeOwnerConfigVisitor visitor; |
| @Mock private Consumer<CodeOwnerConfig.Key> parentCodeOwnersIgnoredCallback; |
| |
| @Inject private ProjectOperations projectOperations; |
| @Inject private DeleteRef deleteRef; |
| |
| private CodeOwnerConfigOperations codeOwnerConfigOperations; |
| private CodeOwnerConfigHierarchy codeOwnerConfigHierarchy; |
| private TestPathExpressions testPathExpressions; |
| |
| @Before |
| public void setUpCodeOwnersPlugin() throws Exception { |
| codeOwnerConfigOperations = |
| plugin.getSysInjector().getInstance(CodeOwnerConfigOperations.class); |
| codeOwnerConfigHierarchy = plugin.getSysInjector().getInstance(CodeOwnerConfigHierarchy.class); |
| testPathExpressions = plugin.getSysInjector().getInstance(TestPathExpressions.class); |
| } |
| |
| @Test |
| public void cannotVisitCodeOwnerConfigsForNullBranch() throws Exception { |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigHierarchy.visit( |
| /* branchNameKey= */ null, |
| getCurrentRevision(BranchNameKey.create(project, "master")), |
| Paths.get("/foo/bar/baz.md"), |
| visitor)); |
| assertThat(npe).hasMessageThat().isEqualTo("branch"); |
| } |
| |
| @Test |
| public void cannotVisitCodeOwnerConfigsForNullRevision() throws Exception { |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigHierarchy.visit( |
| BranchNameKey.create(project, "master"), |
| /* revision= */ null, |
| Paths.get("/foo/bar/baz.md"), |
| visitor)); |
| assertThat(npe).hasMessageThat().isEqualTo("revision"); |
| } |
| |
| @Test |
| public void cannotVisitCodeOwnerConfigsForNullPath() throws Exception { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, "master"); |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigHierarchy.visit( |
| branchNameKey, |
| getCurrentRevision(branchNameKey), |
| /* absolutePath= */ null, |
| visitor)); |
| assertThat(npe).hasMessageThat().isEqualTo("absolutePath"); |
| } |
| |
| @Test |
| public void cannotVisitCodeOwnerConfigsForRelativePath() throws Exception { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, "master"); |
| String relativePath = "foo/bar/baz.md"; |
| IllegalStateException exception = |
| assertThrows( |
| IllegalStateException.class, |
| () -> |
| codeOwnerConfigHierarchy.visit( |
| branchNameKey, |
| getCurrentRevision(branchNameKey), |
| Paths.get(relativePath), |
| visitor)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("path %s must be absolute", relativePath)); |
| } |
| |
| @Test |
| public void cannotVisitCodeOwnerConfigsWithNullVisitor() throws Exception { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, "master"); |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigHierarchy.visit( |
| branchNameKey, |
| getCurrentRevision(branchNameKey), |
| Paths.get("/foo/bar/baz.md"), |
| /* codeOwnerConfigVisitor= */ null)); |
| assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigVisitor"); |
| } |
| |
| @Test |
| public void cannotVisitCodeOwnerConfigsWithNullCallback() throws Exception { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, "master"); |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigHierarchy.visit( |
| branchNameKey, |
| getCurrentRevision(branchNameKey), |
| Paths.get("/foo/bar/baz.md"), |
| visitor, |
| /* parentCodeOwnersIgnoredCallback= */ null)); |
| assertThat(npe).hasMessageThat().isEqualTo("parentCodeOwnersIgnoredCallback"); |
| } |
| |
| @Test |
| public void visitorNotInvokedIfNoCodeOwnerConfigExists() throws Exception { |
| visit("master", "/foo/bar/baz.md"); |
| verifyZeroInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorNotInvokedForNonApplyingCodeOwnerConfig() throws Exception { |
| String branch = "master"; |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/other/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| visit(branch, "/foo/bar/baz.md"); |
| verifyZeroInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorInvokedForApplyingCodeOwnerConfigs() throws Exception { |
| String branch = "master"; |
| |
| CodeOwnerConfig.Key rootCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit(branch, "/foo/bar/baz.md"); |
| |
| // Verify that we received the callbacks in the right order, starting from the folder of the |
| // given path up to the root folder. |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(rootCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorIsNotInvokedForFolderThatHasNoCodeOwnerConfig() throws Exception { |
| String branch = "master"; |
| |
| CodeOwnerConfig.Key rootCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit(branch, "/foo/bar/baz.md"); |
| |
| // Verify that we received the callbacks in the right order, starting from the folder of the |
| // given path up to the root folder. There is no callback for the '/foo/' folder as this folder |
| // doesn't contain a code owner config file. |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(rootCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorInvokedWithCodeOwnerConfigsFromOldRevision() throws Exception { |
| String branch = "master"; |
| |
| CodeOwnerConfig.Key rootCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| RevCommit revision1 = projectOperations.project(project).getHead(branch); |
| CodeOwnerConfig expectedRootCodeOwnerConfig = |
| codeOwnerConfigOperations.codeOwnerConfig(rootCodeOwnerConfigKey).get(); |
| CodeOwnerConfig expectedFooCodeOwnerConfig = |
| codeOwnerConfigOperations.codeOwnerConfig(fooCodeOwnerConfigKey).get(); |
| |
| // Update one code owner config. |
| codeOwnerConfigOperations |
| .codeOwnerConfig(fooCodeOwnerConfigKey) |
| .forUpdate() |
| .ignoreParentCodeOwners() |
| .update(); |
| |
| // Create a new code owner config. |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| RevCommit revision2 = projectOperations.project(project).getHead(branch); |
| assertThat(revision1).isNotEqualTo(revision2); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| codeOwnerConfigHierarchy.visit( |
| BranchNameKey.create(project, branch), revision1, Paths.get("/foo/bar/baz.md"), visitor); |
| |
| // Verify that we received the callbacks for the code owner configs from the old revison, in |
| // the right order, starting from the folder of the given path up to the root folder. |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier.verify(visitor).visit(expectedFooCodeOwnerConfig); |
| orderVerifier.verify(visitor).visit(expectedRootCodeOwnerConfig); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorCanStopTheIterationOverCodeOwnerConfigsByReturningFalse() throws Exception { |
| String branch = "master"; |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| // Return true for the first time the visitor is invoked, and false for all further invocations. |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true).thenReturn(false); |
| visit(branch, "/foo/bar/baz.md"); |
| |
| // Verify that we received the callbacks in the right order, starting from the folder of the |
| // given path up to the root folder. We expect only 2 callbacks, since the visitor returns false |
| // for the second invocation. |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorIsNotInvokedForParentCodeOwnerConfigsIfParentCodeOwnersAreIgnored() |
| throws Exception { |
| String branch = "master"; |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/") |
| .ignoreParentCodeOwners() |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarBazCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/baz") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit(branch, "/foo/bar/baz/README.md"); |
| |
| // Verify that we received the callbacks in the right order, starting from the folder of the |
| // given path up to the root folder. There is no callback for the '/' and '/foo/' folders since |
| // the code owner config for the '/foo/bar/' folder defines that parent code owners should be |
| // ignored. |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarBazCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| |
| verify(parentCodeOwnersIgnoredCallback).accept(fooBarCodeOwnerConfigKey); |
| verifyNoMoreInteractions(parentCodeOwnersIgnoredCallback); |
| } |
| |
| @Test |
| public void |
| visitorIsNotInvokedForParentCodeOwnerConfigsIfAMatchingCodeOwnerSetIgnoresParentCodeOwners() |
| throws Exception { |
| String branch = "master"; |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .setIgnoreGlobalAndParentCodeOwners() |
| .addPathExpression(testPathExpressions.matchAllFilesInSubfolder("baz")) |
| .addCodeOwnerEmail(admin.email()) |
| .build()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarBazCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/baz") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit(branch, "/foo/bar/baz/README.md"); |
| |
| // Verify that we received the callbacks in the right order, starting from the folder of the |
| // given path up to the root folder. There is no callback for the '/' and '/foo/' folders since |
| // the code owner config for the '/foo/bar/' folder contains a matching code owner set that |
| // defines that parent code owners should be |
| // ignored. |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarBazCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| |
| verify(parentCodeOwnersIgnoredCallback).accept(fooBarCodeOwnerConfigKey); |
| verifyNoMoreInteractions(parentCodeOwnersIgnoredCallback); |
| } |
| |
| @Test |
| public void |
| visitorIsInvokedForParentCodeOwnerConfigsIfANonMatchingCodeOwnerSetIgnoresParentCodeOwners() |
| throws Exception { |
| String branch = "master"; |
| |
| CodeOwnerConfig.Key rootCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(branch) |
| .folderPath("/foo/bar/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .setIgnoreGlobalAndParentCodeOwners() |
| .addPathExpression(testPathExpressions.matchFileType("txt")) |
| .addCodeOwnerEmail(admin.email()) |
| .build()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit(branch, "/foo/bar/baz.md"); |
| |
| // Verify that we received the callbacks in the right order, starting from the folder of the |
| // given path up to the root folder. |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooBarCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(fooCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(rootCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| |
| verifyZeroInteractions(parentCodeOwnersIgnoredCallback); |
| } |
| |
| @Test |
| public void visitorInvokedForCodeOwnerConfigInRefsMetaConfig() throws Exception { |
| CodeOwnerConfig.Key metaCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit("master", "/foo/bar/baz.md"); |
| verify(visitor).visit(codeOwnerConfigOperations.codeOwnerConfig(metaCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorNotInvokedForCodeOwnerConfigInRefsMetaConfigIfItDoesntApply() |
| throws Exception { |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath("/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addPathExpression(testPathExpressions.matchAllFilesInSubfolder("other")) |
| .addCodeOwnerEmail(admin.email()) |
| .build()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit("master", "/foo/bar/baz.md"); |
| verifyZeroInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorInvokedForCodeOwnerConfigInRefsMetaConfigWithMatchingPathExpression() |
| throws Exception { |
| CodeOwnerConfig.Key metaCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath("/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addPathExpression(testPathExpressions.matchAllFilesInSubfolder("foo")) |
| .addCodeOwnerEmail(admin.email()) |
| .build()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit("master", "/foo/bar/baz.md"); |
| verify(visitor).visit(codeOwnerConfigOperations.codeOwnerConfig(metaCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void visitorForCodeOwnerConfigInRefsMetaConfigInvokedLast() throws Exception { |
| CodeOwnerConfig.Key metaCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key rootCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addPathExpression(testPathExpressions.matchAllFilesInSubfolder("other")) |
| .addCodeOwnerEmail(admin.email()) |
| .build()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit("master", "/foo/bar/baz.md"); |
| |
| InOrder orderVerifier = Mockito.inOrder(visitor); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(rootCodeOwnerConfigKey).get()); |
| orderVerifier |
| .verify(visitor) |
| .visit(codeOwnerConfigOperations.codeOwnerConfig(metaCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void |
| visitorNotInvokedForCodeOwnerConfigInRefsMetaConfigIfRootCodeOwnerConfigIgnoresParent() |
| throws Exception { |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key rootCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .ignoreParentCodeOwners() |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit("master", "/foo/bar/baz.md"); |
| verify(visitor).visit(codeOwnerConfigOperations.codeOwnerConfig(rootCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| |
| verify(parentCodeOwnersIgnoredCallback).accept(rootCodeOwnerConfigKey); |
| verifyNoMoreInteractions(parentCodeOwnersIgnoredCallback); |
| } |
| |
| @Test |
| public void visitorCanStopTheIterationAtRoot() throws Exception { |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| CodeOwnerConfig.Key rootCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .ignoreParentCodeOwners() |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(false); |
| visit("master", "/foo/bar/baz.md"); |
| verify(visitor).visit(codeOwnerConfigOperations.codeOwnerConfig(rootCodeOwnerConfigKey).get()); |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void |
| visitorIsOnlyInvokedOnceForDefaultCodeOnwerConfigFileIfConfigsInRefsMetaConxfigAreIterated() |
| throws Exception { |
| CodeOwnerConfig.Key metaCodeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath("/") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| |
| when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true); |
| visit(RefNames.REFS_CONFIG, "/foo/bar/baz.md"); |
| |
| // Verify that we received the callback for the code owner config only once. |
| verify(visitor).visit(codeOwnerConfigOperations.codeOwnerConfig(metaCodeOwnerConfigKey).get()); |
| |
| verifyNoMoreInteractions(visitor); |
| } |
| |
| @Test |
| public void refsMetaConfigBranchIsMissing() throws Exception { |
| ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project)); |
| deleteRef.deleteSingleRef(projectState, RefNames.REFS_CONFIG); |
| |
| // Visit '/foo/bar/baz.md' in master. This would callback for these OWNERS files if they |
| // existed: |
| // 1. '/foo/bar/OWNERS' in master |
| // 2. '/foo/OWNERS' in master |
| // 3. '/OWNERS' in master |
| // 4. '/OWNERS' in refs/meta/config (this is where the default code owners are loaded from) |
| // This test is making sure that trying to load 4. doesn't fail if refs/meta/config doesn't |
| // exist. |
| visit("master", "/foo/bar/baz.md"); |
| verifyZeroInteractions(visitor); |
| } |
| |
| private void visit(String branchName, String path) |
| throws InvalidPluginConfigurationException, IOException { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, branchName); |
| codeOwnerConfigHierarchy.visit( |
| branchNameKey, |
| getCurrentRevision(branchNameKey), |
| Paths.get(path), |
| visitor, |
| parentCodeOwnersIgnoredCallback); |
| } |
| |
| private ObjectId getCurrentRevision(BranchNameKey branchNameKey) throws IOException { |
| try (Repository repository = repoManager.openRepository(branchNameKey.project()); |
| RevWalk rw = new RevWalk(repository)) { |
| Ref ref = repository.exactRef(branchNameKey.branch()); |
| checkNotNull( |
| ref, |
| "branch %s in repository %s not found", |
| branchNameKey.branch(), |
| branchNameKey.project().get()); |
| return rw.parseCommit(ref.getObjectId()); |
| } |
| } |
| } |