diff --git a/build.gradle b/build.gradle index 0a67a795..5db65967 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ dependencies { compile ('org.apache.jclouds:jclouds-core:1.9.2') compile ('org.apache.jclouds.driver:jclouds-okhttp:1.9.2') compile ('com.google.auto.service:auto-service:1.0-rc2') - compile ('com.google.auto.value:auto-value:1.1') + compile ('com.google.auto.value:auto-value:1.2') testCompile ('org.apache.jclouds:jclouds-core:1.9.2:tests') testCompile ('org.testng:testng:6.8.21') diff --git a/gradle.properties b/gradle.properties index 76bbdf66..682bd01d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = com.cdancy -version = 0.0.10 +version = 0.0.11-SNAPSHOT artifactoryURL = http://127.0.0.1:8080/artifactory artifactoryUser = admin diff --git a/src/main/java/com/cdancy/bitbucket/rest/domain/common/ErrorsWrapper.java b/src/main/java/com/cdancy/bitbucket/rest/domain/common/ErrorsWrapper.java new file mode 100644 index 00000000..752e976a --- /dev/null +++ b/src/main/java/com/cdancy/bitbucket/rest/domain/common/ErrorsWrapper.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.cdancy.bitbucket.rest.domain.common; + +import java.util.List; + +import com.cdancy.bitbucket.rest.error.Error; + +public interface ErrorsWrapper { + + public abstract List errors(); +} diff --git a/src/main/java/com/cdancy/bitbucket/rest/domain/common/Page.java b/src/main/java/com/cdancy/bitbucket/rest/domain/common/Page.java new file mode 100644 index 00000000..0d0b6654 --- /dev/null +++ b/src/main/java/com/cdancy/bitbucket/rest/domain/common/Page.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.cdancy.bitbucket.rest.domain.common; + +import java.util.List; + +public interface Page { + + public abstract int start(); + + public abstract int limit(); + + public abstract int size(); + + public abstract int nextPageStart(); + + public abstract boolean isLastPage(); + + public abstract List values(); +} diff --git a/src/main/java/com/cdancy/bitbucket/rest/domain/common/Utils.java b/src/main/java/com/cdancy/bitbucket/rest/domain/common/Utils.java new file mode 100644 index 00000000..5709a95a --- /dev/null +++ b/src/main/java/com/cdancy/bitbucket/rest/domain/common/Utils.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.cdancy.bitbucket.rest.domain.common; + +import java.util.List; + +import com.google.common.collect.ImmutableList; + +public class Utils { + public static List nullToEmpty(Iterable input) { + return input == null ? ImmutableList. of() : ImmutableList.copyOf(input); + } +} diff --git a/src/main/java/com/cdancy/bitbucket/rest/domain/project/ProjectPage.java b/src/main/java/com/cdancy/bitbucket/rest/domain/project/ProjectPage.java new file mode 100644 index 00000000..9cd483a3 --- /dev/null +++ b/src/main/java/com/cdancy/bitbucket/rest/domain/project/ProjectPage.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.cdancy.bitbucket.rest.domain.project; + +import java.util.List; + +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +import com.cdancy.bitbucket.rest.domain.common.ErrorsWrapper; +import com.cdancy.bitbucket.rest.domain.common.Page; +import com.cdancy.bitbucket.rest.domain.common.Utils; +import com.cdancy.bitbucket.rest.error.Error; +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class ProjectPage implements Page, ErrorsWrapper { + + @SerializedNames({ "start", "limit", "size", "nextPageStart", "isLastPage", "values", "errors" }) + public static ProjectPage create(int start, int limit, int size, int nextPageStart, boolean isLastPage, + @Nullable List values, @Nullable List errors) { + return new AutoValue_ProjectPage(start, limit, size, nextPageStart, isLastPage, + Utils.nullToEmpty(values), Utils.nullToEmpty(errors)); + } +} diff --git a/src/main/java/com/cdancy/bitbucket/rest/domain/repository/RepositoryPage.java b/src/main/java/com/cdancy/bitbucket/rest/domain/repository/RepositoryPage.java new file mode 100644 index 00000000..ad7e686f --- /dev/null +++ b/src/main/java/com/cdancy/bitbucket/rest/domain/repository/RepositoryPage.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.cdancy.bitbucket.rest.domain.repository; + +import java.util.List; + +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +import com.cdancy.bitbucket.rest.domain.common.ErrorsWrapper; +import com.cdancy.bitbucket.rest.domain.common.Page; +import com.cdancy.bitbucket.rest.domain.common.Utils; +import com.cdancy.bitbucket.rest.error.Error; +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class RepositoryPage implements Page, ErrorsWrapper { + + @SerializedNames({ "start", "limit", "size", "nextPageStart", "isLastPage", "values", "errors" }) + public static RepositoryPage create(int start, int limit, int size, int nextPageStart, boolean isLastPage, + @Nullable List values, @Nullable List errors) { + return new AutoValue_RepositoryPage(start, limit, size, nextPageStart, isLastPage, + Utils.nullToEmpty(values), Utils.nullToEmpty(errors)); + } +} diff --git a/src/main/java/com/cdancy/bitbucket/rest/features/ProjectApi.java b/src/main/java/com/cdancy/bitbucket/rest/features/ProjectApi.java index 70b231ad..51f570bb 100644 --- a/src/main/java/com/cdancy/bitbucket/rest/features/ProjectApi.java +++ b/src/main/java/com/cdancy/bitbucket/rest/features/ProjectApi.java @@ -17,18 +17,7 @@ package com.cdancy.bitbucket.rest.features; -import com.cdancy.bitbucket.rest.domain.project.Project; -import com.cdancy.bitbucket.rest.domain.pullrequest.PullRequest; -import com.cdancy.bitbucket.rest.fallbacks.BitbucketFallbacks; -import com.cdancy.bitbucket.rest.filters.BitbucketAuthentication; -import com.cdancy.bitbucket.rest.options.CreateProject; -import org.jclouds.rest.annotations.BinderParam; -import org.jclouds.rest.annotations.Fallback; -import org.jclouds.rest.annotations.RequestFilters; -import org.jclouds.rest.binders.BindToJsonPayload; - import javax.inject.Named; - import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -36,9 +25,21 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; - +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.cdancy.bitbucket.rest.domain.project.Project; +import com.cdancy.bitbucket.rest.domain.project.ProjectPage; +import com.cdancy.bitbucket.rest.fallbacks.BitbucketFallbacks; +import com.cdancy.bitbucket.rest.filters.BitbucketAuthentication; +import com.cdancy.bitbucket.rest.options.CreateProject; + @Produces(MediaType.APPLICATION_JSON) @RequestFilters(BitbucketAuthentication.class) @Path("/rest/api/{jclouds.api-version}/projects") @@ -63,4 +64,13 @@ public interface ProjectApi { @Fallback(BitbucketFallbacks.FalseOnError.class) @DELETE boolean delete(@PathParam("project") String project); + + @GET + @Named("project:list") + @Consumes(MediaType.APPLICATION_JSON) + @Fallback(BitbucketFallbacks.ProjectOnError.class) + ProjectPage list(@Nullable @QueryParam("start") Integer start, + @Nullable @QueryParam("limit") Integer limit, + @Nullable @QueryParam("name") String name, + @Nullable @QueryParam("permission") String permission); } diff --git a/src/main/java/com/cdancy/bitbucket/rest/features/RepositoryApi.java b/src/main/java/com/cdancy/bitbucket/rest/features/RepositoryApi.java index 908d0fb8..2a4bb85e 100644 --- a/src/main/java/com/cdancy/bitbucket/rest/features/RepositoryApi.java +++ b/src/main/java/com/cdancy/bitbucket/rest/features/RepositoryApi.java @@ -17,17 +17,7 @@ package com.cdancy.bitbucket.rest.features; -import com.cdancy.bitbucket.rest.domain.repository.Repository; -import com.cdancy.bitbucket.rest.fallbacks.BitbucketFallbacks; -import com.cdancy.bitbucket.rest.filters.BitbucketAuthentication; -import com.cdancy.bitbucket.rest.options.CreateRepository; -import org.jclouds.rest.annotations.BinderParam; -import org.jclouds.rest.annotations.Fallback; -import org.jclouds.rest.annotations.RequestFilters; -import org.jclouds.rest.binders.BindToJsonPayload; - import javax.inject.Named; - import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -35,9 +25,21 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; - +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.cdancy.bitbucket.rest.domain.repository.Repository; +import com.cdancy.bitbucket.rest.domain.repository.RepositoryPage; +import com.cdancy.bitbucket.rest.fallbacks.BitbucketFallbacks; +import com.cdancy.bitbucket.rest.filters.BitbucketAuthentication; +import com.cdancy.bitbucket.rest.options.CreateRepository; + @Produces(MediaType.APPLICATION_JSON) @RequestFilters(BitbucketAuthentication.class) @Path("/rest/api/{jclouds.api-version}/projects") @@ -66,4 +68,13 @@ Repository get(@PathParam("project") String project, @DELETE boolean delete(@PathParam("project") String project, @PathParam("repo") String repo); + + @GET + @Named("repository:list") + @Consumes(MediaType.APPLICATION_JSON) + @Path("/{project}/repos") + @Fallback(BitbucketFallbacks.RepositoryOnError.class) + RepositoryPage list(@PathParam("project") String project, + @Nullable @QueryParam("start") Integer start, + @Nullable @QueryParam("limit") Integer limit); } diff --git a/src/test/java/com/cdancy/bitbucket/rest/features/CommentsApiMockTest.java b/src/test/java/com/cdancy/bitbucket/rest/features/CommentsApiMockTest.java index 30afc949..00443cbe 100644 --- a/src/test/java/com/cdancy/bitbucket/rest/features/CommentsApiMockTest.java +++ b/src/test/java/com/cdancy/bitbucket/rest/features/CommentsApiMockTest.java @@ -17,6 +17,13 @@ package com.cdancy.bitbucket.rest.features; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Map; + +import org.testng.annotations.Test; + import com.cdancy.bitbucket.rest.BitbucketApi; import com.cdancy.bitbucket.rest.BitbucketApiMetadata; import com.cdancy.bitbucket.rest.domain.comment.Anchor; @@ -24,13 +31,9 @@ import com.cdancy.bitbucket.rest.domain.comment.Parent; import com.cdancy.bitbucket.rest.internal.BaseBitbucketMockTest; import com.cdancy.bitbucket.rest.options.CreateComment; +import com.google.common.collect.ImmutableMap; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; /** * Mock tests for the {@link CommentsApi} class. @@ -113,8 +116,10 @@ public void testDeleteComment() throws Exception { boolean pr = api.delete("PRJ", "my-repo", 101, 1, 1); assertNotNull(pr); assertTrue(pr); + + Map queryParams = ImmutableMap.of("version", 1); assertSent(server, "DELETE", "/rest/api/" + BitbucketApiMetadata.API_VERSION - + "/projects/PRJ/repos/my-repo/pull-requests/101/comments/1?version=1"); + + "/projects/PRJ/repos/my-repo/pull-requests/101/comments/1", queryParams); } finally { baseApi.close(); server.shutdown(); diff --git a/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiLiveTest.java b/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiLiveTest.java index aa049aaf..043fe582 100644 --- a/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiLiveTest.java +++ b/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiLiveTest.java @@ -17,20 +17,33 @@ package com.cdancy.bitbucket.rest.features; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.List; + +import org.assertj.core.api.Condition; +import org.testng.annotations.Test; + import com.cdancy.bitbucket.rest.BaseBitbucketApiLiveTest; import com.cdancy.bitbucket.rest.domain.project.Project; +import com.cdancy.bitbucket.rest.domain.project.ProjectPage; import com.cdancy.bitbucket.rest.options.CreateProject; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @Test(groups = "live", testName = "ProjectApiLiveTest", singleThreaded = true) public class ProjectApiLiveTest extends BaseBitbucketApiLiveTest { String projectKey = randomStringLettersOnly(); + Condition withProjectKey = new Condition() { + @Override + public boolean matches(Project value) { + return value.key().equals(projectKey); + } + }; + @Test public void testCreateProject() { CreateProject createProject = CreateProject.create(projectKey, null, null, null); @@ -56,6 +69,19 @@ public void testDeleteProject() { assertTrue(success); } + @Test(dependsOnMethods = "testGetProject") + public void testListProjects() { + ProjectPage projectPage = api().list(0, 100, null, null); + + assertNotNull(projectPage); + assertThat(projectPage.errors()).isEmpty(); + assertThat(projectPage.size()).isGreaterThan(0); + + List projects = projectPage.values(); + assertThat(projects).isNotEmpty(); + assertThat(projects).areExactly(1, withProjectKey); + } + @Test public void testDeleteProjectNonExistent() { boolean success = api().delete(randomStringLettersOnly()); diff --git a/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiMockTest.java b/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiMockTest.java index 0b5ce105..2d64d6ad 100644 --- a/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiMockTest.java +++ b/src/test/java/com/cdancy/bitbucket/rest/features/ProjectApiMockTest.java @@ -17,18 +17,24 @@ package com.cdancy.bitbucket.rest.features; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Map; + +import org.testng.annotations.Test; + import com.cdancy.bitbucket.rest.BitbucketApi; import com.cdancy.bitbucket.rest.BitbucketApiMetadata; import com.cdancy.bitbucket.rest.domain.project.Project; +import com.cdancy.bitbucket.rest.domain.project.ProjectPage; import com.cdancy.bitbucket.rest.internal.BaseBitbucketMockTest; import com.cdancy.bitbucket.rest.options.CreateProject; +import com.google.common.collect.ImmutableMap; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; /** * Mock tests for the {@link ProjectApi} class. @@ -147,4 +153,64 @@ public void testDeleteProjectNonExistent() throws Exception { server.shutdown(); } } + + public void testGetProjectList() throws Exception { + MockWebServer server = mockEtcdJavaWebServer(); + + server.enqueue(new MockResponse().setBody(payloadFromResource("/project-page-full.json")).setResponseCode(200)); + try (BitbucketApi baseApi = api(server.getUrl("/"))) { + ProjectApi api = baseApi.projectApi(); + + ProjectPage projectPage = api.list(null, null, null, null); + + assertSent(server, "GET", "/rest/api/" + BitbucketApiMetadata.API_VERSION + "/projects"); + + assertNotNull(projectPage); + assertThat(projectPage.errors()).isEmpty(); + + int size = projectPage.size(); + int limit = projectPage.limit(); + + assertThat(size).isLessThanOrEqualTo(limit); + assertThat(projectPage.start()).isEqualTo(0); + assertThat(projectPage.isLastPage()).isTrue(); + + assertThat(projectPage.values()).hasSize(size); + assertThat(projectPage.values()).hasOnlyElementsOfType(Project.class); + } finally { + server.shutdown(); + } + } + + public void testGetProjectListWithLimit() throws Exception { + MockWebServer server = mockEtcdJavaWebServer(); + + server.enqueue(new MockResponse().setBody(payloadFromResource("/project-page-truncated.json")).setResponseCode(200)); + try (BitbucketApi baseApi = api(server.getUrl("/"))) { + ProjectApi api = baseApi.projectApi(); + + int start = 0; + int limit = 2; + ProjectPage projectPage = api.list(start, limit, null, null); + + Map queryParams = ImmutableMap.of("start", start, "limit", limit); + assertSent(server, "GET", "/rest/api/" + BitbucketApiMetadata.API_VERSION + "/projects", queryParams); + + assertNotNull(projectPage); + assertThat(projectPage.errors()).isEmpty(); + + int size = projectPage.size(); + + assertThat(size).isEqualTo(limit); + assertThat(projectPage.start()).isEqualTo(start); + assertThat(projectPage.limit()).isEqualTo(limit); + assertThat(projectPage.isLastPage()).isFalse(); + assertThat(projectPage.nextPageStart()).isEqualTo(size); + + assertThat(projectPage.values()).hasSize(size); + assertThat(projectPage.values()).hasOnlyElementsOfType(Project.class); + } finally { + server.shutdown(); + } + } } diff --git a/src/test/java/com/cdancy/bitbucket/rest/features/PullRequestApiMockTest.java b/src/test/java/com/cdancy/bitbucket/rest/features/PullRequestApiMockTest.java index 040e2919..2c367584 100644 --- a/src/test/java/com/cdancy/bitbucket/rest/features/PullRequestApiMockTest.java +++ b/src/test/java/com/cdancy/bitbucket/rest/features/PullRequestApiMockTest.java @@ -21,21 +21,22 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -import com.cdancy.bitbucket.rest.domain.pullrequest.PagedChangeResponse; -import com.cdancy.bitbucket.rest.domain.pullrequest.PagedCommitResponse; +import java.util.Map; + import org.testng.annotations.Test; import com.cdancy.bitbucket.rest.BitbucketApi; import com.cdancy.bitbucket.rest.BitbucketApiMetadata; import com.cdancy.bitbucket.rest.domain.pullrequest.MergeStatus; import com.cdancy.bitbucket.rest.domain.pullrequest.MinimalRepository; +import com.cdancy.bitbucket.rest.domain.pullrequest.PagedChangeResponse; +import com.cdancy.bitbucket.rest.domain.pullrequest.PagedCommitResponse; import com.cdancy.bitbucket.rest.domain.pullrequest.ProjectKey; import com.cdancy.bitbucket.rest.domain.pullrequest.PullRequest; import com.cdancy.bitbucket.rest.domain.pullrequest.Reference; - -import com.cdancy.bitbucket.rest.options.CreatePullRequest; - import com.cdancy.bitbucket.rest.internal.BaseBitbucketMockTest; +import com.cdancy.bitbucket.rest.options.CreatePullRequest; +import com.google.common.collect.ImmutableMap; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; @@ -112,8 +113,10 @@ public void testDeclinePullRequest() throws Exception { assertTrue(pr.id() == 101); assertTrue(pr.state().equalsIgnoreCase("DECLINED")); assertFalse(pr.open()); + + Map queryParams = ImmutableMap.of("version", 1); assertSent(server, "POST", "/rest/api/" + BitbucketApiMetadata.API_VERSION - + "/projects/PRJ/repos/my-repo/pull-requests/101/decline?version=1"); + + "/projects/PRJ/repos/my-repo/pull-requests/101/decline", queryParams); } finally { baseApi.close(); server.shutdown(); @@ -135,8 +138,10 @@ public void testReopenPullRequest() throws Exception { assertTrue(pr.id() == 101); assertTrue(pr.state().equalsIgnoreCase("OPEN")); assertTrue(pr.open()); + + Map queryParams = ImmutableMap.of("version", 1); assertSent(server, "POST", "/rest/api/" + BitbucketApiMetadata.API_VERSION - + "/projects/PRJ/repos/my-repo/pull-requests/101/reopen?version=1"); + + "/projects/PRJ/repos/my-repo/pull-requests/101/reopen", queryParams); } finally { baseApi.close(); @@ -199,8 +204,10 @@ public void testMergePullRequest() throws Exception { assertTrue(pr.id() == 101); assertTrue(pr.state().equalsIgnoreCase("MERGED")); assertFalse(pr.open()); + + Map queryParams = ImmutableMap.of("version", 1); assertSent(server, "POST", "/rest/api/" + BitbucketApiMetadata.API_VERSION - + "/projects/PRJ/repos/my-repo/pull-requests/101/merge?version=1"); + + "/projects/PRJ/repos/my-repo/pull-requests/101/merge", queryParams); } finally { baseApi.close(); server.shutdown(); @@ -240,8 +247,10 @@ public void testGetPullRequestChanges() throws Exception { assertTrue(pr.errors().size() == 0); assertTrue(pr.values().size() == 1); assertNotNull(pr); + + Map queryParams = ImmutableMap.of("withComments", true, "limit", 12); assertSent(server, "GET", "/rest/api/" + BitbucketApiMetadata.API_VERSION - + "/projects/PRJ/repos/my-repo/pull-requests/101/changes?withComments=true&limit=12"); + + "/projects/PRJ/repos/my-repo/pull-requests/101/changes", queryParams); } finally { baseApi.close(); server.shutdown(); @@ -262,8 +271,10 @@ public void testGetPullRequestCommits() throws Exception { assertTrue(pr.errors().size() == 0); assertTrue(pr.values().size() == 1); assertTrue(pr.totalCount() == 1); + + Map queryParams = ImmutableMap.of("withCounts", true, "limit", 1); assertSent(server, "GET", "/rest/api/" + BitbucketApiMetadata.API_VERSION - + "/projects/PRJ/repos/my-repo/pull-requests/101/commits?withCounts=true&limit=1"); + + "/projects/PRJ/repos/my-repo/pull-requests/101/commits", queryParams); } finally { baseApi.close(); server.shutdown(); diff --git a/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiLiveTest.java b/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiLiveTest.java index cc4a8882..a6246a68 100644 --- a/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiLiveTest.java +++ b/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiLiveTest.java @@ -17,17 +17,23 @@ package com.cdancy.bitbucket.rest.features; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.List; + +import org.assertj.core.api.Condition; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + import com.cdancy.bitbucket.rest.BaseBitbucketApiLiveTest; import com.cdancy.bitbucket.rest.domain.project.Project; import com.cdancy.bitbucket.rest.domain.repository.Repository; +import com.cdancy.bitbucket.rest.domain.repository.RepositoryPage; import com.cdancy.bitbucket.rest.options.CreateProject; import com.cdancy.bitbucket.rest.options.CreateRepository; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @Test(groups = "live", testName = "RepositoryApiLiveTest", singleThreaded = true) public class RepositoryApiLiveTest extends BaseBitbucketApiLiveTest { @@ -35,6 +41,13 @@ public class RepositoryApiLiveTest extends BaseBitbucketApiLiveTest { String projectKey = randomStringLettersOnly(); String repoKey = randomStringLettersOnly(); + Condition withRepositorySlug = new Condition() { + @Override + public boolean matches(Repository value) { + return value.slug().equals(repoKey); + } + }; + @BeforeClass public void init() { CreateProject createProject = CreateProject.create(projectKey, null, null, null); @@ -67,6 +80,19 @@ public void testDeleteRepository() { assertTrue(success); } + @Test(dependsOnMethods = "testGetRepository") + public void testListProjects() { + RepositoryPage repositoryPage = api().list(projectKey, 0, 100); + + assertNotNull(repositoryPage); + assertThat(repositoryPage.errors()).isEmpty(); + assertThat(repositoryPage.size()).isGreaterThan(0); + + List repositories = repositoryPage.values(); + assertThat(repositories).isNotEmpty(); + assertThat(repositories).areExactly(1, withRepositorySlug); + } + @Test public void testDeleteRepositoryNonExistent() { boolean success = api().delete(projectKey, randomStringLettersOnly()); diff --git a/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiMockTest.java b/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiMockTest.java index 87070b75..2accfc0d 100644 --- a/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiMockTest.java +++ b/src/test/java/com/cdancy/bitbucket/rest/features/RepositoryApiMockTest.java @@ -17,16 +17,21 @@ package com.cdancy.bitbucket.rest.features; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import java.util.Map; + import org.testng.annotations.Test; import com.cdancy.bitbucket.rest.BitbucketApi; import com.cdancy.bitbucket.rest.BitbucketApiMetadata; import com.cdancy.bitbucket.rest.domain.repository.Repository; +import com.cdancy.bitbucket.rest.domain.repository.RepositoryPage; import com.cdancy.bitbucket.rest.internal.BaseBitbucketMockTest; import com.cdancy.bitbucket.rest.options.CreateRepository; +import com.google.common.collect.ImmutableMap; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; @@ -151,4 +156,66 @@ public void testDeleteRepositoryNonExistent() throws Exception { server.shutdown(); } } + + public void testGetRepositoryList() throws Exception { + MockWebServer server = mockEtcdJavaWebServer(); + + server.enqueue(new MockResponse().setBody(payloadFromResource("/repository-page-full.json")).setResponseCode(200)); + try (BitbucketApi baseApi = api(server.getUrl("/"))) { + RepositoryApi api = baseApi.repositoryApi(); + + String projectKey = "PRJ1"; + RepositoryPage repositoryPage = api.list(projectKey, null, null); + + assertSent(server, "GET", "/rest/api/" + BitbucketApiMetadata.API_VERSION + "/projects/" + projectKey + "/repos"); + + assertNotNull(repositoryPage); + assertThat(repositoryPage.errors()).isEmpty(); + + int size = repositoryPage.size(); + int limit = repositoryPage.limit(); + + assertThat(size).isLessThanOrEqualTo(limit); + assertThat(repositoryPage.start()).isEqualTo(0); + assertThat(repositoryPage.isLastPage()).isTrue(); + + assertThat(repositoryPage.values()).hasSize(size); + assertThat(repositoryPage.values()).hasOnlyElementsOfType(Repository.class); + } finally { + server.shutdown(); + } + } + + public void testGetRepositoryListWithLimit() throws Exception { + MockWebServer server = mockEtcdJavaWebServer(); + + server.enqueue(new MockResponse().setBody(payloadFromResource("/repository-page-truncated.json")).setResponseCode(200)); + try (BitbucketApi baseApi = api(server.getUrl("/"))) { + RepositoryApi api = baseApi.repositoryApi(); + + String projectKey = "PRJ1"; + int start = 0; + int limit = 2; + RepositoryPage repositoryPage = api.list(projectKey, start, limit); + + Map queryParams = ImmutableMap.of("start", start, "limit", limit); + assertSent(server, "GET", "/rest/api/" + BitbucketApiMetadata.API_VERSION + "/projects/" + projectKey + "/repos", queryParams); + + assertNotNull(repositoryPage); + assertThat(repositoryPage.errors()).isEmpty(); + + int size = repositoryPage.size(); + + assertThat(size).isEqualTo(limit); + assertThat(repositoryPage.start()).isEqualTo(start); + assertThat(repositoryPage.limit()).isEqualTo(limit); + assertThat(repositoryPage.isLastPage()).isFalse(); + assertThat(repositoryPage.nextPageStart()).isEqualTo(size); + + assertThat(repositoryPage.values()).hasSize(size); + assertThat(repositoryPage.values()).hasOnlyElementsOfType(Repository.class); + } finally { + server.shutdown(); + } + } } diff --git a/src/test/java/com/cdancy/bitbucket/rest/internal/BaseBitbucketMockTest.java b/src/test/java/com/cdancy/bitbucket/rest/internal/BaseBitbucketMockTest.java index 05545204..4b591e30 100644 --- a/src/test/java/com/cdancy/bitbucket/rest/internal/BaseBitbucketMockTest.java +++ b/src/test/java/com/cdancy/bitbucket/rest/internal/BaseBitbucketMockTest.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.net.URL; +import java.util.Map; import java.util.Properties; import javax.ws.rs.core.HttpHeaders; @@ -34,7 +35,10 @@ import com.cdancy.bitbucket.rest.BitbucketApi; import com.google.common.base.Charsets; +import com.google.common.base.Functions; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import com.google.gson.JsonParser; import com.squareup.okhttp.mockwebserver.MockWebServer; import com.squareup.okhttp.mockwebserver.RecordedRequest; @@ -66,7 +70,8 @@ protected Properties setupProperties() { * Create a MockWebServer. * * @return instance of MockWebServer - * @throws IOException if unable to start/play server + * @throws IOException + * if unable to start/play server */ public static MockWebServer mockEtcdJavaWebServer() throws IOException { MockWebServer server = new MockWebServer(); @@ -77,7 +82,8 @@ public static MockWebServer mockEtcdJavaWebServer() throws IOException { /** * Get the String representation of some resource to be used as payload. * - * @param resource String representation of a given resource + * @param resource + * String representation of a given resource * @return payload in String form */ public String payloadFromResource(String resource) { @@ -88,11 +94,44 @@ public String payloadFromResource(String resource) { } } + private static Map extractParams(String path) { + + int qmIndex = path.indexOf('?'); + if (qmIndex <= 0) { + return ImmutableMap.of(); + } + + ImmutableMap.Builder builder = ImmutableMap.builder(); + + String[] params = path.substring(qmIndex + 1).split("&"); + for (int i = 0; i < params.length; i++) { + String[] keyValue = params[i].split("=", 2); + if (keyValue.length > 1) { + builder.put(keyValue[0], keyValue[1]); + } + } + + return builder.build(); + } + protected RecordedRequest assertSent(MockWebServer server, String method, String path) throws InterruptedException { + return assertSent(server, method, path, ImmutableMap. of()); + } + + protected RecordedRequest assertSent(MockWebServer server, String method, String expectedPath, Map queryParams) + throws InterruptedException { + RecordedRequest request = server.takeRequest(); assertThat(request.getMethod()).isEqualTo(method); - assertThat(request.getPath()).isEqualTo(path); assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON); + + String path = request.getPath(); + String rawPath = path.contains("?") ? path.substring(0, path.indexOf('?')) : path; + assertThat(rawPath).isEqualTo(expectedPath); + + Map normalizedParams = Maps.transformValues(queryParams, Functions.toStringFunction()); + assertThat(normalizedParams).isEqualTo(extractParams(path)); + return request; } diff --git a/src/test/resources/project-page-full.json b/src/test/resources/project-page-full.json new file mode 100644 index 00000000..90cbb057 --- /dev/null +++ b/src/test/resources/project-page-full.json @@ -0,0 +1,53 @@ +{ + "size": 3, + "limit": 25, + "start": 0, + "isLastPage": true, + "values": [ + { + "key": "PRJ1", + "id": 1, + "name": "Project 1", + "description": "Mock project #1", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1" + } + ] + } + }, + { + "key": "PRJ2", + "id": 2, + "name": "Project 2", + "description": "Mock project #2", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ2" + } + ] + } + }, + { + "key": "PRJ3", + "id": 3, + "name": "Project 3", + "description": "Mock project #3", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ3" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/project-page-truncated.json b/src/test/resources/project-page-truncated.json new file mode 100644 index 00000000..61eede2e --- /dev/null +++ b/src/test/resources/project-page-truncated.json @@ -0,0 +1,39 @@ +{ + "size": 2, + "limit": 2, + "start": 0, + "isLastPage": false, + "nextPageStart": 2, + "values": [ + { + "key": "PRJ1", + "id": 1, + "name": "Project 1", + "description": "Mock project #1", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1" + } + ] + } + }, + { + "key": "PRJ2", + "id": 2, + "name": "Project 2", + "description": "Mock project #2", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ2" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/repository-page-full.json b/src/test/resources/repository-page-full.json new file mode 100644 index 00000000..4a9b64a9 --- /dev/null +++ b/src/test/resources/repository-page-full.json @@ -0,0 +1,134 @@ +{ + "size": 3, + "limit": 25, + "start": 0, + "isLastPage": true, + "values": [ + { + "slug": "repo-1", + "id": 101, + "name": "repo-1", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "PRJ1", + "id": 1, + "name": "Project 1", + "description": "Mock project #1", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "https://127.0.0.1/scm/PRJ1/repo-1.git", + "name": "http" + }, + { + "href": "ssh://git@127.0.0.1/PRJ1/repo-1.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1/repos/repo-1/browse" + } + ] + } + }, + { + "slug": "repo-2", + "id": 102, + "name": "repo-2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "PRJ1", + "id": 1, + "name": "Project 1", + "description": "Mock project #1", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "https://127.0.0.1/scm/PRJ1/repo-2.git", + "name": "http" + }, + { + "href": "ssh://git@127.0.0.1/PRJ1/repo-2.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1/repos/repo-2/browse" + } + ] + } + }, + { + "slug": "repo-3", + "id": 103, + "name": "repo-3", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "PRJ1", + "id": 1, + "name": "Project 1", + "description": "Mock project #1", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "https://127.0.0.1/scm/PRJ1/repo-3.git", + "name": "http" + }, + { + "href": "ssh://git@127.0.0.1/PRJ1/repo-3.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1/repos/repo-3/browse" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/repository-page-truncated.json b/src/test/resources/repository-page-truncated.json new file mode 100644 index 00000000..ac9941a1 --- /dev/null +++ b/src/test/resources/repository-page-truncated.json @@ -0,0 +1,93 @@ +{ + "size": 2, + "limit": 2, + "start": 0, + "isLastPage": false, + "nextPageStart": 2, + "values": [ + { + "slug": "repo-1", + "id": 101, + "name": "repo-1", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "PRJ1", + "id": 1, + "name": "Project 1", + "description": "Mock project #1", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "https://127.0.0.1/scm/PRJ1/repo-1.git", + "name": "http" + }, + { + "href": "ssh://git@127.0.0.1/PRJ1/repo-1.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1/repos/repo-1/browse" + } + ] + } + }, + { + "slug": "repo-2", + "id": 102, + "name": "repo-2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "PRJ1", + "id": 1, + "name": "Project 1", + "description": "Mock project #1", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "https://127.0.0.1/scm/PRJ1/repo-2.git", + "name": "http" + }, + { + "href": "ssh://git@127.0.0.1/PRJ1/repo-2.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://127.0.0.1:7990/projects/PRJ1/repos/repo-2/browse" + } + ] + } + } + ] +} \ No newline at end of file