diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt index fc8ccba929..7a07d55019 100644 --- a/Documentation/rest-api-projects.txt +++ b/Documentation/rest-api-projects.txt @@ -52,22 +52,110 @@ by project name. } ---- -.Get all projects with their description -**** -get::/projects/?d -**** +[[project-options]] +==== Project Options + +Branch(b):: +Limit the results to the projects having the specified branch and +include the sha1 of the branch in the results. ++ +Get projects that have a 'master' branch: ++ +.Request +---- +GET /projects/?b=master HTTP/1.0 +---- ++ +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "some-project": { + "id": "some-project", + "branches": { + "master": "c5ed9dfcbf002ca0e432d788dab6ca2387829ca7" + } + }, + "some-other-project": { + "id": "some-other-project", + "branches": { + "master": "ef1c270142f9581ecf768f4193fc8f8a81102ec2" + } + }, + } +---- + +Description(d):: +Include project description in the results. ++ +Get all the projects with their description: ++ +.Request +---- +GET /projects/?d HTTP/1.0 +---- ++ +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "some-project": { + "id": "some-project", + "description": "Description of some project." + }, + "some-other-project": { + "id": "some-other-project", + "description": "Description of some other project." + } + }, + } +---- + +Limit(n):: +Limit the number of projects to be included in the results. ++ +Query the first project in the project list: ++ +.Request +---- + GET /projects/?n=1 HTTP/1.0 +---- ++ +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "some-project": { + "id": "some-project" + } + } +---- + [[suggest-projects]] -The `/projects/` URL also accepts a prefix string in the `p` parameter. -This limits the results to those projects that start with the specified +Prefix(p):: +Limit the results to those projects that start with the specified prefix. ++ List all projects that start with `platform/`: - ++ .Request ---- GET /projects/?p=platform%2F HTTP/1.0 ---- - ++ .Response ---- HTTP/1.1 200 OK @@ -84,22 +172,118 @@ List all projects that start with `platform/`: } } ---- ++ E.g. this feature can be used by suggestion client UI's to limit results. -The `/projects/` URL also accepts a limit integer in the `n` parameter. -This limits the results to show `n` projects. - -Query the first 25 projects in project list. +Skip(S):: +Skip the given number of projects from the beginning of the list. ++ +Query the second project in the project list: ++ +.Request ---- - GET /projects/?n=25 HTTP/1.0 + GET /projects/?n=1&S=1 HTTP/1.0 +---- ++ +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "some-other-project": { + "id": "some-other-project" + } + } ---- -The `/projects/` URL also accepts a start integer in the `S` parameter. -The results will skip `S` projects from project list. - -Query 25 projects starting from index 50. +Substring(m):: +Limit the results to those projects that match the specified substring. ++ +List all projects that match substring `test/`: ++ +.Request ---- - GET /projects/?n=25&S=50 HTTP/1.0 + GET /projects/?m=test%2F HTTP/1.0 +---- ++ +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "test/some-project": { + "id": "test%2Fsome-project", + }, + "some-path/test/some-other-project": { + "id": "some-path%2Ftest%2Fsome-other-project", + } + } +---- + +Tree(t):: +Get projects inheritance in a tree-like format. This option does +not work together with the branch option. ++ +Get all the projects with tree option: ++ +.Request +---- +GET /projects/?t HTTP/1.0 +---- ++ +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "All-Projects" { + "id": "All-Projects" + }, + "child-project": { + "id": "child-project", + "parent":"parent-project" + }, + "parent-project": { + "id": "parent-project", + "parent":"All-Projects" + } + } +---- + +Type(type):: +Get projects with specified type: ALL, CODE, PERMISSIONS. ++ +Get all the projects of type 'PERMISSIONS': ++ +.Request +---- +GET /projects/?type=PERMISSIONS HTTP/1.0 +---- ++ +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "All-Projects" { + "id": "All-Projects" + }, + "some-parent-project": { + "id": "some-parent-project" + } + } ---- [[get-project]] diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java new file mode 100644 index 0000000000..953a21a368 --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java @@ -0,0 +1,211 @@ +// Copyright (C) 2014 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.acceptance.rest.project; + +import static com.google.gerrit.acceptance.GitUtil.createProject; +import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjects; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.RestResponse; +import com.google.gerrit.extensions.api.projects.ProjectInput; +import com.google.gerrit.extensions.common.ProjectInfo; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gerrit.server.config.AllUsersName; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Inject; + +import com.jcraft.jsch.JSchException; + +import org.apache.http.HttpStatus; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; + +public class ListProjectsIT extends AbstractDaemonTest { + + @Inject + private AllProjectsName allProjects; + + @Inject + private AllUsersName allUsers; + + @Test + public void listProjects() throws IOException, JSchException { + Project.NameKey someProject = new Project.NameKey("some-project"); + createProject(sshSession, someProject.get()); + + RestResponse r = GET("/projects/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertProjects(Arrays.asList(allUsers, someProject, project), + result.values()); + } + + @Test + public void listProjectsWithBranch() throws IOException, JSchException { + RestResponse r = GET("/projects/?b=master"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertNotNull(result.get(project.get())); + assertNotNull(result.get(project.get()).branches); + assertEquals(1, result.get(project.get()).branches.size()); + assertNotNull(result.get(project.get()).branches.get("master")); + } + + @Test + public void listProjectWithDescription() throws RestApiException, IOException { + ProjectInput projectInput = new ProjectInput(); + projectInput.name = "some-project"; + projectInput.description = "Description of some-project"; + gApi.projects().name(projectInput.name).create(projectInput); + + // description not be included in the results by default. + RestResponse r = GET("/projects/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertNotNull(result.get(projectInput.name)); + assertNull(result.get(projectInput.name).description); + + r = GET("/projects/?d"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + result = toProjectInfoMap(r); + assertNotNull(result.get(projectInput.name)); + assertEquals(projectInput.description, + result.get(projectInput.name).description); + } + + @Test + public void listProjectsWithLimit() throws IOException, JSchException { + for (int i = 0; i < 5; i++) { + createProject(sshSession, new Project.NameKey("someProject" + i).get()); + } + + RestResponse r = GET("/projects/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertEquals(7, result.size()); // 5 plus 2 existing projects: p and + // All-Users + + r = GET("/projects/?n=2"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + result = toProjectInfoMap(r); + assertEquals(2, result.size()); + } + + @Test + public void listProjectsWithPrefix() throws IOException, JSchException { + Project.NameKey someProject = new Project.NameKey("some-project"); + createProject(sshSession, someProject.get()); + Project.NameKey someOtherProject = + new Project.NameKey("some-other-project"); + createProject(sshSession, someOtherProject.get()); + Project.NameKey projectAwesome = new Project.NameKey("project-awesome"); + createProject(sshSession, projectAwesome.get()); + + RestResponse r = GET("/projects/?p=some"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertProjects(Arrays.asList(someProject, someOtherProject), + result.values()); + } + + @Test + public void listProjectsWithSkip() throws IOException, JSchException { + for (int i = 0; i < 5; i++) { + createProject(sshSession, new Project.NameKey("someProject" + i).get()); + } + + RestResponse r = GET("/projects/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertEquals(7, result.size()); // 5 plus 2 existing projects: p and + // All-Users + + r = GET("/projects/?S=6"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + result = toProjectInfoMap(r); + assertEquals(1, result.size()); + } + + @Test + public void listProjectsWithSubstring() throws IOException, JSchException { + Project.NameKey someProject = new Project.NameKey("some-project"); + createProject(sshSession, someProject.get()); + Project.NameKey someOtherProject = + new Project.NameKey("some-other-project"); + createProject(sshSession, someOtherProject.get()); + Project.NameKey projectAwesome = new Project.NameKey("project-awesome"); + createProject(sshSession, projectAwesome.get()); + + RestResponse r = GET("/projects/?m=some"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertProjects( + Arrays.asList(someProject, someOtherProject, projectAwesome), + result.values()); + } + + @Test + public void listProjectsWithTree() throws IOException, JSchException { + Project.NameKey someParentProject = + new Project.NameKey("some-parent-project"); + createProject(sshSession, someParentProject.get()); + Project.NameKey someChildProject = + new Project.NameKey("some-child-project"); + createProject(sshSession, someChildProject.get(), someParentProject); + + RestResponse r = GET("/projects/?tree"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertNotNull(result.get(someChildProject.get())); + assertEquals(someParentProject.get(), + result.get(someChildProject.get()).parent); + } + + @Test + public void listProjectWithType() throws RestApiException, IOException { + RestResponse r = GET("/projects/?type=PERMISSIONS"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + Map result = toProjectInfoMap(r); + assertEquals(1, result.size()); + assertNotNull(result.get(allProjects.get())); + + r = GET("/projects/?type=ALL"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + result = toProjectInfoMap(r); + assertEquals(3, result.size()); + assertProjects(Arrays.asList(allProjects, allUsers, project), + result.values()); + } + + private static Map toProjectInfoMap(RestResponse r) + throws IOException { + Map result = + newGson().fromJson(r.getReader(), + new TypeToken>() {}.getType()); + return result; + } + + private RestResponse GET(String endpoint) throws IOException { + return adminSession.get(endpoint); + } +} diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java index 3128004844..3354cb81e2 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java @@ -27,18 +27,20 @@ import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.project.ProjectState; -import java.util.List; +import java.util.Collection; import java.util.Set; public class ProjectAssert { public static void assertProjects(Iterable expected, - List actual) { + Collection actual) { for (final Project.NameKey p : expected) { ProjectInfo info = Iterables.find(actual, new Predicate() { @Override public boolean apply(ProjectInfo info) { - return new Project.NameKey(info.name).equals(p); + // 'name' is not set if returned in a map, use the id instead. + return new Project.NameKey(info.name != null ? info.name : Url + .decode(info.id)).equals(p); }}, null); assertNotNull("missing project: " + p, info); actual.remove(info);