From 84d96295996f9054559af85b957b874ee494ed28 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 7 Apr 2021 21:23:44 -0500 Subject: [PATCH] Add gitHubCheckMilestoneHasNoOpenIssues Closes gh-9693 --- RELEASE.adoc | 17 +- build.gradle | 8 + buildSrc/build.gradle | 4 + .../github/milestones/GitHubMilestoneApi.java | 110 +++++ .../GitHubMilestoneHasNoOpenIssuesTask.java | 74 ++++ .../milestones/GitHubMilestonePlugin.java | 38 ++ .../gradle/github/milestones/Milestone.java | 31 ++ .../github/milestones/RepositoryRef.java | 65 +++ .../milestones/GitHubMilestoneApiTests.java | 388 ++++++++++++++++++ .../milestones/GitHubMilestoneApiTests.java | 386 +++++++++++++++++ 10 files changed, 1118 insertions(+), 3 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java create mode 100644 buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java diff --git a/RELEASE.adoc b/RELEASE.adoc index 81947b953a..bfa0567fcd 100644 --- a/RELEASE.adoc +++ b/RELEASE.adoc @@ -49,8 +49,8 @@ $ git checkout - The following command will update the dependencies again but this time creating a ticket for each update and placing `Closes gh-` in the commit. Replacing the following values: - - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `public_repo` - - Replace with the title of the milestone you are releasing now (i.e. 5.5.0-RC1) +* - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `public_repo` +* - Replace with the title of the milestone you are releasing now (i.e. 5.5.0-RC1) [source,bash] ---- @@ -61,7 +61,18 @@ Apply any fixes from your previous branch that were necessary. = Check All Issues are Closed -Check that all issues are closed for the milestone https://github.com/spring-projects/spring-security/milestones +The following command will check if there are any open issues for the ticket. +Before running the command, replace the following values: + +* - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `public_repo`. This is optional since you are unlikely to reach the rate limit for such a simple check. +* - Replace with the title of the milestone you are releasing now (i.e. 5.5.0-RC1) + +[source,bash] +---- +$ ./gradlew gitHubCheckMilestoneHasNoOpenIssues -PgitHubAccessToken= -PnextVersion= +---- + +Alternatively, you can manually check using https://github.com/spring-projects/spring-security/milestones = Update Release Version diff --git a/build.gradle b/build.gradle index 3ce2d137c0..aa1b8d393a 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ apply plugin: 'io.spring.convention.root' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'org.springframework.security.update-dependencies' apply plugin: 'org.springframework.security.sagan' +apply plugin: 'org.springframework.github.milestone' group = 'org.springframework.security' description = 'Spring Security' @@ -34,6 +35,13 @@ tasks.named("saganCreateRelease") { apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/" } +tasks.named("gitHubCheckMilestoneHasNoOpenIssues") { + repository { + owner = "spring-projects" + name = "spring-security" + } +} + updateDependenciesSettings { gitHub { organization = "spring-projects" diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index f1eb85cac3..a3190c148d 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -48,6 +48,10 @@ gradlePlugin { id = "org.springframework.security.sagan" implementationClass = "org.springframework.gradle.sagan.SaganPlugin" } + githubMilestone { + id = "org.springframework.github.milestone" + implementationClass = "org.springframework.gradle.github.milestones.GitHubMilestonePlugin" + } } } diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java new file mode 100644 index 0000000000..31f1274adb --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java @@ -0,0 +1,110 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * 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 + * + * https://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 org.springframework.gradle.github.milestones; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.List; + +public class GitHubMilestoneApi { + private String baseUrl = "https://api.github.com"; + + private OkHttpClient client; + + private Gson gson = new Gson(); + + public GitHubMilestoneApi() { + this.client = new OkHttpClient.Builder().build(); + } + + public GitHubMilestoneApi(String gitHubToken) { + this.client = new OkHttpClient.Builder() + .addInterceptor(new AuthorizationInterceptor(gitHubToken)) + .build(); + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public long findMilestoneNumberByTitle(RepositoryRef repositoryRef, String milestoneTitle) { + String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/milestones?per_page=100"; + Request request = new Request.Builder().get().url(url) + .build(); + try { + Response response = this.client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository " + repositoryRef + ". Response " + response); + } + List milestones = this.gson.fromJson(response.body().charStream(), new TypeToken>(){}.getType()); + for (Milestone milestone : milestones) { + if (milestoneTitle.equals(milestone.getTitle())) { + return milestone.getNumber(); + } + } + if (milestones.size() <= 100) { + throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); + } + throw new RuntimeException("It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); + } catch (IOException e) { + throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef, e); + } + } + + public boolean isOpenIssuesForMilestoneNumber(RepositoryRef repositoryRef, long milestoneNumber) { + String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/issues?per_page=1&milestone=" + milestoneNumber; + Request request = new Request.Builder().get().url(url) + .build(); + try { + Response response = this.client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef + ". Response " + response); + } + List issues = this.gson.fromJson(response.body().charStream(), new TypeToken>(){}.getType()); + return !issues.isEmpty(); + } catch (IOException e) { + throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef, e); + } + } + +// public boolean isOpenIssuesForMilestoneName(String owner, String repository, String milestoneName) { +// +// } + + + private static class AuthorizationInterceptor implements Interceptor { + + private final String token; + + public AuthorizationInterceptor(String token) { + this.token = token; + } + + @Override + public okhttp3.Response intercept(Chain chain) throws IOException { + Request request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer " + this.token).build(); + return chain.proceed(request); + } + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java new file mode 100644 index 0000000000..de846378f7 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * 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 + * + * https://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 org.springframework.gradle.github.milestones; + +import org.gradle.api.Action; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.TaskAction; + +public class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask { + @Input + private RepositoryRef repository = new RepositoryRef(); + + @Input + private String milestoneTitle; + + @Input @Optional + private String gitHubAccessToken; + + private GitHubMilestoneApi milestones = new GitHubMilestoneApi(); + + @TaskAction + public void checkHasNoOpenIssues() { + long milestoneNumber = this.milestones.findMilestoneNumberByTitle(this.repository, this.milestoneTitle); + boolean isOpenIssues = this.milestones.isOpenIssuesForMilestoneNumber(this.repository, milestoneNumber); + if (isOpenIssues) { + throw new IllegalStateException("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber); + } + System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber); + } + + public RepositoryRef getRepository() { + return repository; + } + + public void repository(Action repository) { + repository.execute(this.repository); + } + + public void setRepository(RepositoryRef repository) { + this.repository = repository; + } + + public String getMilestoneTitle() { + return milestoneTitle; + } + + public void setMilestoneTitle(String milestoneTitle) { + this.milestoneTitle = milestoneTitle; + } + + public String getGitHubAccessToken() { + return gitHubAccessToken; + } + + public void setGitHubAccessToken(String gitHubAccessToken) { + this.gitHubAccessToken = gitHubAccessToken; + this.milestones = new GitHubMilestoneApi(gitHubAccessToken); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java new file mode 100644 index 0000000000..527b767613 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * 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 + * + * https://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 org.springframework.gradle.github.milestones; + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +public class GitHubMilestonePlugin implements Plugin { + @Override + public void apply(Project project) { + project.getTasks().register("gitHubCheckMilestoneHasNoOpenIssues", GitHubMilestoneHasNoOpenIssuesTask.class, new Action() { + @Override + public void execute(GitHubMilestoneHasNoOpenIssuesTask githubCheckMilestoneHasNoOpenIssues) { + githubCheckMilestoneHasNoOpenIssues.setGroup("Release"); + githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone"); + githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion")); + if (project.hasProperty("githubAccessToken")) { + githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); + } + } + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java new file mode 100644 index 0000000000..5d0ff23489 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java @@ -0,0 +1,31 @@ +package org.springframework.gradle.github.milestones; + +public class Milestone { + private String title; + + private long number; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public long getNumber() { + return number; + } + + public void setNumber(long number) { + this.number = number; + } + + @Override + public String toString() { + return "Milestone{" + + "title='" + title + '\'' + + ", number=" + number + + '}'; + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java new file mode 100644 index 0000000000..70eec3b150 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java @@ -0,0 +1,65 @@ +package org.springframework.gradle.github.milestones; +public class RepositoryRef { + private String owner; + + private String name; + + RepositoryRef() { + } + + public RepositoryRef(String owner, String name) { + this.owner = owner; + this.name = name; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "RepositoryRef{" + + "owner='" + owner + '\'' + + ", name='" + name + '\'' + + '}'; + } + + public static RepositoryRefBuilder owner(String owner) { + return new RepositoryRefBuilder().owner(owner); + } + + public static final class RepositoryRefBuilder { + private String owner; + private String repository; + + private RepositoryRefBuilder() { + } + + private RepositoryRefBuilder owner(String owner) { + this.owner = owner; + return this; + } + + public RepositoryRefBuilder repository(String repository) { + this.repository = repository; + return this; + } + + public RepositoryRef build() { + return new RepositoryRef(owner, repository); + } + } +} + diff --git a/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java b/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java new file mode 100644 index 0000000000..816b1ad0d4 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java @@ -0,0 +1,388 @@ +package io.spring.gradle.github.milestones; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.gradle.github.milestones.GitHubMilestoneApi; +import org.springframework.gradle.github.milestones.RepositoryRef; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + + +public class GitHubMilestoneApiTests { + private GitHubMilestoneApi github; + + private RepositoryRef repositoryRef = RepositoryRef.owner("spring-projects").repository("spring-security").build(); + + private MockWebServer server; + + private String baseUrl; + + @Before + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.github = new GitHubMilestoneApi("mock-oauth-token"); + this.baseUrl = this.server.url("/api").toString(); + this.github.setBaseUrl(this.baseUrl); + } + + @After + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void findMilestoneNumberByTitleWhenFoundThenSuccess() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + long milestoneNumberByTitle = this.github.findMilestoneNumberByTitle(this.repositoryRef, "5.5.0-RC1"); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100"); + + assertThat(milestoneNumberByTitle).isEqualTo(191); + } + + @Test + public void findMilestoneNumberByTitleWhenNotFoundThenException() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> this.github.findMilestoneNumberByTitle(this.repositoryRef, "missing")); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenAllClosedThenFalse() throws Exception { + String responseJson = "[]"; + long milestoneNumber = 202; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isFalse(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenOpenIssuesThenTrue() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562\",\n" + + " \"repository_url\":\"https://api.github.com/repos/spring-projects/spring-security\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/labels{/name}\",\n" + + " \"comments_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/comments\",\n" + + " \"events_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/events\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"id\":851886504,\n" + + " \"node_id\":\"MDExOlB1bGxSZXF1ZXN0NjEwMjMzMDcw\",\n" + + " \"number\":9562,\n" + + " \"title\":\"Add package-list\",\n" + + " \"user\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"labels\":[\n" + + " {\n" + + " \"id\":322225043,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNDM=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/in:%20build\",\n" + + " \"name\":\"in: build\",\n" + + " \"color\":\"e8f9de\",\n" + + " \"default\":false,\n" + + " \"description\":\"An issue in the build\"\n" + + " },\n" + + " {\n" + + " \"id\":322225079,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNzk=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/type:%20bug\",\n" + + " \"name\":\"type: bug\",\n" + + " \"color\":\"e3d9fc\",\n" + + " \"default\":false,\n" + + " \"description\":\"A general bug\"\n" + + " }\n" + + " ],\n" + + " \"state\":\"open\",\n" + + " \"locked\":false,\n" + + " \"assignee\":{\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"assignees\":[\n" + + " {\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " }\n" + + " ],\n" + + " \"milestone\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " },\n" + + " \"comments\":0,\n" + + " \"created_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"updated_at\":\"2021-04-07T17:00:00Z\",\n" + + " \"closed_at\":null,\n" + + " \"author_association\":\"MEMBER\",\n" + + " \"active_lock_reason\":null,\n" + + " \"pull_request\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/pulls/9562\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"diff_url\":\"https://github.com/spring-projects/spring-security/pull/9562.diff\",\n" + + " \"patch_url\":\"https://github.com/spring-projects/spring-security/pull/9562.patch\"\n" + + " },\n" + + " \"body\":\"Closes gh-9528\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\n" + + " \"performed_via_github_app\":null\n" + + " }\n" + + "]"; + long milestoneNumber = 191; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isTrue(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java b/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java new file mode 100644 index 0000000000..11840c7701 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java @@ -0,0 +1,386 @@ +package org.springframework.gradle.github.milestones; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + + +public class GitHubMilestoneApiTests { + private GitHubMilestoneApi github; + + private RepositoryRef repositoryRef = RepositoryRef.owner("spring-projects").repository("spring-security").build(); + + private MockWebServer server; + + private String baseUrl; + + @Before + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.github = new GitHubMilestoneApi("mock-oauth-token"); + this.baseUrl = this.server.url("/api").toString(); + this.github.setBaseUrl(this.baseUrl); + } + + @After + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void findMilestoneNumberByTitleWhenFoundThenSuccess() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + long milestoneNumberByTitle = this.github.findMilestoneNumberByTitle(this.repositoryRef, "5.5.0-RC1"); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100"); + + assertThat(milestoneNumberByTitle).isEqualTo(191); + } + + @Test + public void findMilestoneNumberByTitleWhenNotFoundThenException() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> this.github.findMilestoneNumberByTitle(this.repositoryRef, "missing")); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenAllClosedThenFalse() throws Exception { + String responseJson = "[]"; + long milestoneNumber = 202; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isFalse(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenOpenIssuesThenTrue() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562\",\n" + + " \"repository_url\":\"https://api.github.com/repos/spring-projects/spring-security\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/labels{/name}\",\n" + + " \"comments_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/comments\",\n" + + " \"events_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/events\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"id\":851886504,\n" + + " \"node_id\":\"MDExOlB1bGxSZXF1ZXN0NjEwMjMzMDcw\",\n" + + " \"number\":9562,\n" + + " \"title\":\"Add package-list\",\n" + + " \"user\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"labels\":[\n" + + " {\n" + + " \"id\":322225043,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNDM=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/in:%20build\",\n" + + " \"name\":\"in: build\",\n" + + " \"color\":\"e8f9de\",\n" + + " \"default\":false,\n" + + " \"description\":\"An issue in the build\"\n" + + " },\n" + + " {\n" + + " \"id\":322225079,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNzk=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/type:%20bug\",\n" + + " \"name\":\"type: bug\",\n" + + " \"color\":\"e3d9fc\",\n" + + " \"default\":false,\n" + + " \"description\":\"A general bug\"\n" + + " }\n" + + " ],\n" + + " \"state\":\"open\",\n" + + " \"locked\":false,\n" + + " \"assignee\":{\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"assignees\":[\n" + + " {\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " }\n" + + " ],\n" + + " \"milestone\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " },\n" + + " \"comments\":0,\n" + + " \"created_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"updated_at\":\"2021-04-07T17:00:00Z\",\n" + + " \"closed_at\":null,\n" + + " \"author_association\":\"MEMBER\",\n" + + " \"active_lock_reason\":null,\n" + + " \"pull_request\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/pulls/9562\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"diff_url\":\"https://github.com/spring-projects/spring-security/pull/9562.diff\",\n" + + " \"patch_url\":\"https://github.com/spring-projects/spring-security/pull/9562.patch\"\n" + + " },\n" + + " \"body\":\"Closes gh-9528\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\n" + + " \"performed_via_github_app\":null\n" + + " }\n" + + "]"; + long milestoneNumber = 191; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isTrue(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + +}