| // 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.testing.GerritJUnit.assertThrows; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.entities.BranchNameKey; |
| import com.google.gerrit.plugins.codeowners.JgitPath; |
| import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest; |
| import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations; |
| import com.google.inject.Inject; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Optional; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| 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 CodeOwnerConfigFileUpdateScanner}. */ |
| public class CodeOwnerConfigFileUpdateScannerTest extends AbstractCodeOwnersTest { |
| @Rule public final MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); |
| |
| @Mock private CodeOwnerConfigFileUpdater updater; |
| |
| @Inject private ProjectOperations projectOperations; |
| |
| private CodeOwnerConfigOperations codeOwnerConfigOperations; |
| private CodeOwnerConfigFileUpdateScanner codeOwnerConfigFileUpdateScanner; |
| |
| @Before |
| public void setUpCodeOwnersPlugin() throws Exception { |
| codeOwnerConfigOperations = |
| plugin.getSysInjector().getInstance(CodeOwnerConfigOperations.class); |
| codeOwnerConfigFileUpdateScanner = |
| plugin.getSysInjector().getInstance(CodeOwnerConfigFileUpdateScanner.class); |
| } |
| |
| @Test |
| public void cannotUpdateCodeOwnerConfigsForNullBranch() throws Exception { |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigFileUpdateScanner.update( |
| /* branchNameKey= */ null, |
| "Update code owner configs", |
| (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> Optional.empty())); |
| assertThat(npe).hasMessageThat().isEqualTo("branchNameKey"); |
| } |
| |
| @Test |
| public void cannotUpdateCodeOwnerConfigsWithNullCommitMessage() throws Exception { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, "master"); |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigFileUpdateScanner.update( |
| branchNameKey, |
| /* commitMessage= */ null, |
| (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> Optional.empty())); |
| assertThat(npe).hasMessageThat().isEqualTo("commitMessage"); |
| } |
| |
| @Test |
| public void cannotUpdateCodeOwnerConfigsWithNullUpdater() throws Exception { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, "master"); |
| NullPointerException npe = |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| codeOwnerConfigFileUpdateScanner.update( |
| branchNameKey, |
| "Update code owner configs", |
| /* codeOwnerConfigFileUpdater= */ null)); |
| assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigFileUpdater"); |
| } |
| |
| @Test |
| public void cannotUpdateCodeOwnerConfigsForNonExistingBranch() throws Exception { |
| BranchNameKey branchNameKey = BranchNameKey.create(project, "non-existing"); |
| IllegalStateException exception = |
| assertThrows( |
| IllegalStateException.class, |
| () -> |
| codeOwnerConfigFileUpdateScanner.update( |
| branchNameKey, |
| "Update code owner configs", |
| (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> Optional.empty())); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo( |
| String.format( |
| "branch %s of project %s not found", branchNameKey.branch(), project.get())); |
| } |
| |
| @Test |
| public void noUpdateIfNoCodeOwnerConfigFilesExists() throws Exception { |
| Optional<RevCommit> commit = |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), "Update code owner configs", updater); |
| assertThat(commit).isEmpty(); |
| verifyZeroInteractions(updater); |
| } |
| |
| @Test |
| public void noUpdateForNonCodeOwnerConfigFiles() throws Exception { |
| // Create some non code owner config files. |
| try (TestRepository<Repository> testRepo = |
| new TestRepository<>(repoManager.openRepository(project))) { |
| Ref ref = testRepo.getRepository().exactRef("refs/heads/master"); |
| RevCommit head = testRepo.getRevWalk().parseCommit(ref.getObjectId()); |
| testRepo.update( |
| "refs/heads/master", |
| testRepo |
| .commit() |
| .parent(head) |
| .message("Add some non code owner config files") |
| .add("owners.txt", "some content") |
| .add("owners", "some content") |
| .add("foo/bar/owners.txt", "some content") |
| .add("foo/bar/owners", "some content")); |
| } |
| |
| Optional<RevCommit> commit = |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), "Update code owner configs", updater); |
| assertThat(commit).isEmpty(); |
| verifyZeroInteractions(updater); |
| } |
| |
| @Test |
| public void noUpdateIfCallbackDoesntReturnNewFileContent() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .fileName("OWNERS") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| Path path = |
| Paths.get(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath()); |
| String content = codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent(); |
| |
| RevCommit oldHead = projectOperations.project(project).getHead("master"); |
| |
| when(updater.update(path, content)).thenReturn(Optional.empty()); |
| Optional<RevCommit> commit = |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), "Update code owner configs", updater); |
| assertThat(commit).isEmpty(); |
| |
| // Verify the code owner config file was not updated. |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent()) |
| .isEqualTo(content); |
| |
| // Check that no commit was created. |
| RevCommit newHead = projectOperations.project(project).getHead("master"); |
| assertThat(newHead).isEqualTo(oldHead); |
| } |
| |
| @Test |
| public void updateCodeOwnerConfigFiles() throws Exception { |
| TestAccount user2 = accountCreator.user2(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey1 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .fileName("OWNERS") |
| .addCodeOwnerEmail(admin.email()) |
| .create(); |
| Path path1 = |
| Paths.get(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getFilePath()); |
| String oldContent1 = |
| codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getContent(); |
| String newContent1 = user.email() + "\n"; |
| when(updater.update(path1, oldContent1)).thenReturn(Optional.of(newContent1)); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey2 = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .fileName("OWNERS") |
| .addCodeOwnerEmail(user.email()) |
| .create(); |
| Path path2 = |
| Paths.get(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getFilePath()); |
| String oldContent2 = |
| codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getContent(); |
| String newContent2 = user2.email() + "\n"; |
| when(updater.update(path2, oldContent2)).thenReturn(Optional.of(newContent2)); |
| |
| RevCommit oldHead = projectOperations.project(project).getHead("master"); |
| |
| String commitMessage = "Update code owner configs"; |
| Optional<RevCommit> commit = |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), commitMessage, updater); |
| assertThat(commit).isPresent(); |
| |
| // Verify that we received the expected callbacks for the invalid code onwer config. |
| Mockito.verify(updater).update(path1, oldContent1); |
| Mockito.verify(updater).update(path2, oldContent2); |
| verifyNoMoreInteractions(updater); |
| |
| // Verify the code owner config files were updated. |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getContent()) |
| .isEqualTo(newContent1); |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getContent()) |
| .isEqualTo(newContent2); |
| |
| // Check that exactly 1 commit was created. |
| RevCommit newHead = projectOperations.project(project).getHead("master"); |
| assertThat(commit.get()).isEqualTo(newHead); |
| assertThat(newHead).isNotEqualTo(oldHead); |
| assertThat(newHead.getShortMessage()).isEqualTo(commitMessage); |
| assertThat(newHead.getParent(0)).isEqualTo(oldHead); |
| } |
| |
| @Test |
| public void updateInvalidCodeOwnerConfigFile() throws Exception { |
| CodeOwnerConfig.Key codeOwnerConfigKey = createInvalidCodeOwnerConfig("/OWNERS", "INVALID"); |
| |
| when(updater.update(any(Path.class), any(String.class))) |
| .thenReturn(Optional.of("STILL INVALID")); |
| Optional<RevCommit> update = |
| codeOwnerConfigFileUpdateScanner.update( |
| BranchNameKey.create(project, "master"), "Update code owner configs", updater); |
| assertThat(update).isPresent(); |
| |
| // Verify that we received the expected callbacks for the invalid code onwer config. |
| Mockito.verify(updater).update(Paths.get("/OWNERS"), "INVALID"); |
| verifyNoMoreInteractions(updater); |
| |
| // Verify the code owner config file was updated. |
| assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent()) |
| .isEqualTo("STILL INVALID"); |
| } |
| |
| private CodeOwnerConfig.Key createInvalidCodeOwnerConfig(String filePath, String content) |
| throws Exception { |
| disableCodeOwnersForProject(project); |
| String changeId = |
| createChange("Add invalid code owners file", JgitPath.of(filePath).get(), content) |
| .getChangeId(); |
| approve(changeId); |
| gApi.changes().id(changeId).current().submit(); |
| enableCodeOwnersForProject(project); |
| Path path = Paths.get(filePath); |
| return CodeOwnerConfig.Key.create( |
| project, "master", path.getParent().toString(), path.getFileName().toString()); |
| } |
| } |