| // 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.googlesource.gerrit.plugins.deleteproject; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.gerrit.acceptance.GitUtil.pushHead; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.MoreObjects; |
| import com.google.gerrit.acceptance.GerritConfig; |
| import com.google.gerrit.acceptance.LightweightPluginDaemonTest; |
| import com.google.gerrit.acceptance.RestResponse; |
| import com.google.gerrit.acceptance.TestPlugin; |
| import com.google.gerrit.acceptance.UseLocalDisk; |
| import com.google.gerrit.acceptance.UseSsh; |
| import com.google.gerrit.common.data.Permission; |
| import com.google.gerrit.extensions.client.ProjectState; |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.reviewdb.client.RefNames; |
| import com.google.gerrit.server.git.ProjectConfig; |
| import com.googlesource.gerrit.plugins.deleteproject.DeleteProject.Input; |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.stream.Stream; |
| import org.apache.commons.io.FileUtils; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.transport.PushResult; |
| import org.eclipse.jgit.transport.RemoteRefUpdate; |
| import org.eclipse.jgit.transport.RemoteRefUpdate.Status; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| @UseSsh |
| @TestPlugin( |
| name = "delete-project", |
| sysModule = "com.googlesource.gerrit.plugins.deleteproject.Module", |
| sshModule = "com.googlesource.gerrit.plugins.deleteproject.SshModule", |
| httpModule = "com.googlesource.gerrit.plugins.deleteproject.HttpModule") |
| public class DeleteProjectIT extends LightweightPluginDaemonTest { |
| |
| private static final String PLUGIN = "delete-project"; |
| private static final String ARCHIVE_FOLDER = "archiveFolder"; |
| private static final String PARENT_FOLDER = "parentFolder"; |
| |
| private File archiveFolder; |
| private File projectDir; |
| |
| @Before |
| public void setUpArchiveFolder() throws IOException { |
| archiveFolder = Files.createDirectories(Paths.get(ARCHIVE_FOLDER)).toFile(); |
| projectDir = verifyProjectRepoExists(project); |
| } |
| |
| @After |
| public void removeArchiveFolder() { |
| FileUtils.deleteQuietly(archiveFolder); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testHttpDeleteProjectForce() throws Exception { |
| RestResponse r = httpDeleteProjectHelper(true); |
| r.assertNoContent(); |
| assertThat(projectDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testHttpDeleteProjectNotForce() throws Exception { |
| createChange(); |
| RestResponse r = httpDeleteProjectHelper(false); |
| r.assertConflict(); |
| assertThat(projectDir.exists()).isTrue(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testHttpDeleteProjectWithWatches() throws Exception { |
| watch(project.get(), null); |
| RestResponse r = httpDeleteProjectHelper(true); |
| r.assertNoContent(); |
| assertThat(projectDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testSshDeleteProjectWithoutOptions() throws Exception { |
| createChange(); |
| String cmd = Joiner.on(" ").join(PLUGIN, "delete", project.get()); |
| String expected = |
| String.format( |
| "Really delete '%s'?\n" |
| + "This is an operation which permanently deletes data. This cannot be undone!\n" |
| + "If you are sure you wish to delete this project, re-run with the --yes-really-delete flag.\n\n", |
| project.get()); |
| adminSshSession.exec(cmd); |
| |
| assertThat(projectDir.exists()).isTrue(); |
| assertThat(adminSshSession.getError()).isEqualTo(expected); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testSshDeleteProjYesReallyDelete() throws Exception { |
| createChange(); |
| String cmd = createDeleteCommand(project.get()); |
| String expected = |
| String.format( |
| "Project '%s' has open changes. - To really delete '%s', re-run with the --force flag.%n", |
| project.get(), project.get()); |
| adminSshSession.exec(cmd); |
| |
| assertThat(projectDir.exists()).isTrue(); |
| assertThat(adminSshSession.getError()).isEqualTo(expected); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testSshDeleteProjYesReallyDeleteForce() throws Exception { |
| createChange(); |
| String cmd = createDeleteCommand("--force", project.get()); |
| adminSshSession.exec(cmd); |
| assertThat(adminSshSession.getError()).isNull(); |
| assertThat(projectDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| @GerritConfig(name = "plugin.delete-project.enablePreserveOption", value = "true") |
| public void testSshDeleteProjPreserveGitRepoEnabled() throws Exception { |
| String cmd = createDeleteCommand("--preserve-git-repository", project.get()); |
| adminSshSession.exec(cmd); |
| |
| assertThat(adminSshSession.getError()).isNull(); |
| assertThat(projectDir.exists()).isTrue(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| @GerritConfig(name = "plugin.delete-project.enablePreserveOption", value = "false") |
| public void testSshDeleteProjPreserveGitRepoNotEnabled() throws Exception { |
| String cmd = createDeleteCommand("--preserve-git-repository", project.get()); |
| adminSshSession.exec(cmd); |
| String expected = |
| "Given the enablePreserveOption is configured to be false, " |
| + "the --preserve-git-repository option is not allowed.\n" |
| + "Please remove this option and retry.\n"; |
| assertThat(adminSshSession.getError()).isEqualTo(expected); |
| assertThat(projectDir.exists()).isTrue(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| @GerritConfig(name = "plugin.delete-project.hideProjectOnPreserve", value = "true") |
| public void testSshHideProject() throws Exception { |
| String cmd = createDeleteCommand("--preserve-git-repository", project.get()); |
| adminSshSession.exec(cmd); |
| |
| ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); |
| ProjectState state = cfg.getProject().getState(); |
| |
| assertThat(state).isEqualTo(ProjectState.HIDDEN); |
| assertThat(adminSshSession.getError()).isNull(); |
| assertThat(projectDir.exists()).isTrue(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testDeleteProjWithChildren() throws Exception { |
| String childrenString = createProject("foo", project, true).get(); |
| verifyProjectRepoExists(Project.NameKey.parse(childrenString)); |
| |
| String cmd = createDeleteCommand(project.get()); |
| adminSshSession.exec(cmd); |
| assertThat(adminSshSession.getError()) |
| .isEqualTo( |
| "fatal: Cannot delete project because it has children: " + childrenString + "\n"); |
| assertThat(projectDir.exists()).isTrue(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| public void testDeleteAllProject() throws Exception { |
| String name = allProjects.get(); |
| String cmd = createDeleteCommand(name); |
| adminSshSession.exec(cmd); |
| |
| assertThat(adminSshSession.getError()) |
| .isEqualTo("fatal: Cannot delete project because it is protected against deletion" + "\n"); |
| } |
| |
| @Test |
| @UseLocalDisk |
| @GerritConfig(name = "plugin.delete-project.allowDeletionOfReposWithTags", value = "false") |
| public void testDeleteProjWithTags() throws Exception { |
| grant(Permission.CREATE, project, "refs/tags/*", false, REGISTERED_USERS); |
| pushTagOldCommitNotForce(Status.OK); |
| |
| String cmd = createDeleteCommand(project.get()); |
| adminSshSession.exec(cmd); |
| assertThat(adminSshSession.getError()) |
| .isEqualTo("fatal: Project " + project.get() + " has tags" + "\n"); |
| assertThat(projectDir.exists()).isTrue(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| @GerritConfig(name = "plugin.delete-project.archiveDeletedRepos", value = "true") |
| @GerritConfig(name = "plugin.delete-project.archiveFolder", value = ARCHIVE_FOLDER) |
| public void testArchiveProject() throws Exception { |
| assertThat(archiveFolder.exists()).isTrue(); |
| assertThat(isEmpty(archiveFolder.toPath())).isTrue(); |
| |
| String cmd = createDeleteCommand(project.get()); |
| adminSshSession.exec(cmd); |
| |
| assertThat(adminSshSession.getError()).isNull(); |
| assertThat(isEmpty(archiveFolder.toPath())).isFalse(); |
| assertThat(containsDeletedProject(archiveFolder.toPath(), project.get())).isTrue(); |
| assertThat(projectDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @UseLocalDisk |
| @GerritConfig(name = "plugin.delete-project.archiveDeletedRepos", value = "true") |
| @GerritConfig(name = "plugin.delete-project.archiveFolder", value = ARCHIVE_FOLDER) |
| public void testDeleteAndArchiveProjectWithParentFolder() throws Exception { |
| assertThat(archiveFolder.exists()).isTrue(); |
| assertThat(isEmpty(archiveFolder.toPath())).isTrue(); |
| |
| String name = "pj1"; |
| String projectName = createProject(name).get(); |
| File projectDir = verifyProjectRepoExists(Project.NameKey.parse(projectName)); |
| |
| Path parentFolder = projectDir.toPath().getParent().resolve(PARENT_FOLDER).resolve(projectName); |
| parentFolder.toFile().mkdirs(); |
| assertThat(parentFolder.toFile().exists()).isTrue(); |
| assertThat(isEmpty(parentFolder)).isTrue(); |
| |
| Files.move(projectDir.toPath(), parentFolder, REPLACE_EXISTING); |
| assertThat(parentFolder.toFile().exists()).isTrue(); |
| assertThat(isEmpty(parentFolder)).isFalse(); |
| |
| String cmd = createDeleteCommand(PARENT_FOLDER + "/" + projectName); |
| adminSshSession.exec(cmd); |
| |
| assertThat(adminSshSession.getError()).isNull(); |
| assertThat(isEmpty(archiveFolder.toPath())).isFalse(); |
| assertThat(containsDeletedProject(archiveFolder.toPath().resolve(PARENT_FOLDER), name)) |
| .isTrue(); |
| assertThat(projectDir.exists()).isFalse(); |
| |
| assertThat(parentFolder.toFile().exists()).isFalse(); |
| } |
| |
| private File verifyProjectRepoExists(Project.NameKey name) throws IOException { |
| File projectDir; |
| try (Repository projectRepo = repoManager.openRepository(name)) { |
| projectDir = projectRepo.getDirectory(); |
| } |
| assertThat(projectDir.exists()).isTrue(); |
| return projectDir; |
| } |
| |
| private RestResponse httpDeleteProjectHelper(boolean force) throws Exception { |
| setApiUser(user); |
| sender.clear(); |
| String endPoint = "/projects/" + project.get() + "/delete-project~delete"; |
| Input i = new Input(); |
| i.force = force; |
| |
| return adminRestSession.post(endPoint, i); |
| } |
| |
| private String createDeleteCommand(String cmd, String... params) { |
| return Joiner.on(" ") |
| .join(PLUGIN, "delete", "--yes-really-delete", cmd, Joiner.on(" ").join(params)); |
| } |
| |
| private String pushTagOldCommitNotForce(Status expectedStatus) throws Exception { |
| testRepo = cloneProject(project, user); |
| commitBuilder().ident(user.getIdent()).message("subject (" + System.nanoTime() + ")").create(); |
| String tagName = MoreObjects.firstNonNull(null, "v1_" + System.nanoTime()); |
| |
| grant(Permission.SUBMIT, project, "refs/for/refs/heads/master", false, REGISTERED_USERS); |
| pushHead(testRepo, "refs/for/master%submit"); |
| |
| String tagRef = RefNames.REFS_TAGS + tagName; |
| PushResult r = pushHead(testRepo, tagRef, false, false); |
| RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef); |
| assertThat(refUpdate.getStatus()).named("LIGHTWEIGHT").isEqualTo(expectedStatus); |
| return tagName; |
| } |
| |
| private boolean isEmpty(Path dir) throws IOException { |
| try (Stream<Path> dirStream = Files.list(dir)) { |
| return !dirStream.iterator().hasNext(); |
| } |
| } |
| |
| private boolean containsDeletedProject(Path dir, String projectName) throws IOException { |
| try (Stream<Path> dirStream = Files.list(dir)) { |
| return dirStream.anyMatch(d -> d.toString().contains(projectName)); |
| } |
| } |
| } |