diff --git a/build.gradle b/build.gradle index 1250adf358..e52918acd1 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ apply plugin: 'org.springframework.security.update-dependencies' apply plugin: 'org.springframework.security.sagan' apply plugin: 'org.springframework.github.milestone' apply plugin: 'org.springframework.github.changelog' +apply plugin: 'org.springframework.github.release' group = 'org.springframework.security' description = 'Spring Security' @@ -46,6 +47,13 @@ tasks.named("gitHubCheckMilestoneHasNoOpenIssues") { } } +tasks.named("createGitHubRelease") { + repository { + owner = "spring-projects" + name = "spring-security" + } +} + tasks.named("updateDependencies") { // we aren't Gradle 7 compatible yet checkForGradleUpdate = false diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 3530e43393..37651ef076 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -59,6 +59,10 @@ gradlePlugin { id = "org.springframework.github.changelog" implementationClass = "org.springframework.gradle.github.changelog.GitHubChangelogPlugin" } + githubRelease { + id = "org.springframework.github.release" + implementationClass = "org.springframework.gradle.github.release.GitHubReleasePlugin" + } s101 { id = "s101" implementationClass = "s101.S101Plugin" diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java b/buildSrc/src/main/java/org/springframework/gradle/github/RepositoryRef.java similarity index 93% rename from buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java rename to buildSrc/src/main/java/org/springframework/gradle/github/RepositoryRef.java index 70eec3b150..e570a47e90 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java +++ b/buildSrc/src/main/java/org/springframework/gradle/github/RepositoryRef.java @@ -1,10 +1,11 @@ -package org.springframework.gradle.github.milestones; +package org.springframework.gradle.github; + public class RepositoryRef { private String owner; private String name; - RepositoryRef() { + public RepositoryRef() { } public RepositoryRef(String owner, String name) { @@ -62,4 +63,3 @@ public class RepositoryRef { } } } - diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/changelog/GitHubChangelogPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/github/changelog/GitHubChangelogPlugin.java index 2000e1a378..0eab3d8006 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/github/changelog/GitHubChangelogPlugin.java +++ b/buildSrc/src/main/java/org/springframework/gradle/github/changelog/GitHubChangelogPlugin.java @@ -16,6 +16,9 @@ package org.springframework.gradle.github.changelog; +import java.io.File; +import java.nio.file.Paths; + import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -28,12 +31,10 @@ import org.gradle.api.artifacts.repositories.IvyArtifactRepository; import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout; import org.gradle.api.tasks.JavaExec; -import java.io.File; -import java.nio.file.Paths; - public class GitHubChangelogPlugin implements Plugin { public static final String CHANGELOG_GENERATOR_CONFIGURATION_NAME = "changelogGenerator"; + public static final String RELEASE_NOTES_PATH = "changelog/release-notes.md"; @Override public void apply(Project project) { @@ -42,7 +43,7 @@ public class GitHubChangelogPlugin implements Plugin { project.getTasks().register("generateChangelog", JavaExec.class, new Action() { @Override public void execute(JavaExec generateChangelog) { - File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), "changelog/release-notes.md")); + File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), RELEASE_NOTES_PATH)); outputFile.getParentFile().mkdirs(); generateChangelog.setGroup("Release"); generateChangelog.setDescription("Generates the changelog"); 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 index 31f1274adb..fd3c0d817b 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java @@ -16,6 +16,9 @@ package org.springframework.gradle.github.milestones; +import java.io.IOException; +import java.util.List; + import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import okhttp3.Interceptor; @@ -23,8 +26,7 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import java.io.IOException; -import java.util.List; +import org.springframework.gradle.github.RepositoryRef; public class GitHubMilestoneApi { private String baseUrl = "https://api.github.com"; 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 index de846378f7..40b026c804 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java @@ -21,6 +21,8 @@ import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; +import org.springframework.gradle.github.RepositoryRef; + public class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask { @Input private RepositoryRef repository = new RepositoryRef(); diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/release/CreateGitHubReleaseTask.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/CreateGitHubReleaseTask.java new file mode 100644 index 0000000000..65c8b687be --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/CreateGitHubReleaseTask.java @@ -0,0 +1,130 @@ +/* + * Copyright 2002-2021 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.release; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.gradle.api.Action; +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.TaskAction; + +import org.springframework.gradle.github.RepositoryRef; +import org.springframework.gradle.github.changelog.GitHubChangelogPlugin; + +/** + * @author Steve Riesenberg + */ +public class CreateGitHubReleaseTask extends DefaultTask { + @Input + private RepositoryRef repository = new RepositoryRef(); + + @Input @Optional + private String gitHubAccessToken; + + @Input + private String version; + + @Input @Optional + private String branch = "main"; + + @Input + private boolean createRelease = false; + + @TaskAction + public void createGitHubRelease() { + String body = readReleaseNotes(); + Release release = Release.tag(this.version) + .commit(this.branch) + .name(this.version) + .body(body) + .preRelease(this.version.contains("-")) + .build(); + + System.out.printf("%sCreating GitHub release for %s/%s@%s\n", + this.createRelease ? "" : "[DRY RUN] ", + this.repository.getOwner(), + this.repository.getName(), + this.version + ); + System.out.printf(" Release Notes:\n\n----\n%s\n----\n\n", body.trim()); + + if (this.createRelease) { + GitHubReleaseApi github = new GitHubReleaseApi(this.gitHubAccessToken); + github.publishRelease(this.repository, release); + } + } + + private String readReleaseNotes() { + Project project = getProject(); + File inputFile = project.file(Paths.get(project.getBuildDir().getPath(), GitHubChangelogPlugin.RELEASE_NOTES_PATH)); + try { + return Files.readString(inputFile.toPath()); + } catch (IOException ex) { + throw new RuntimeException("Unable to read release notes from " + inputFile, ex); + } + } + + public RepositoryRef getRepository() { + return repository; + } + + public void repository(Action repository) { + repository.execute(this.repository); + } + + public void setRepository(RepositoryRef repository) { + this.repository = repository; + } + + public String getGitHubAccessToken() { + return gitHubAccessToken; + } + + public void setGitHubAccessToken(String gitHubAccessToken) { + this.gitHubAccessToken = gitHubAccessToken; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getBranch() { + return branch; + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public boolean isCreateRelease() { + return createRelease; + } + + public void setCreateRelease(boolean createRelease) { + this.createRelease = createRelease; + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleaseApi.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleaseApi.java new file mode 100644 index 0000000000..65238d0b82 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleaseApi.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2021 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.release; + +import java.io.IOException; + +import com.google.gson.Gson; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import org.springframework.gradle.github.RepositoryRef; + +/** + * Manage GitHub releases. + * + * @author Steve Riesenberg + */ +public class GitHubReleaseApi { + private String baseUrl = "https://api.github.com"; + + private final OkHttpClient httpClient; + private Gson gson = new Gson(); + + public GitHubReleaseApi(String gitHubAccessToken) { + this.httpClient = new OkHttpClient.Builder() + .addInterceptor(new AuthorizationInterceptor(gitHubAccessToken)) + .build(); + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + /** + * Publish a release with no binary attachments. + * + * @param repository The repository owner/name + * @param release The contents of the release + */ + public void publishRelease(RepositoryRef repository, Release release) { + String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/releases"; + String json = this.gson.toJson(release); + RequestBody body = RequestBody.create(MediaType.parse("application/json"), json); + Request request = new Request.Builder().url(url).post(body).build(); + try { + Response response = this.httpClient.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException(String.format("Could not create release %s for repository %s/%s. Got response %s", + release.getName(), repository.getOwner(), repository.getName(), response)); + } + } catch (IOException ex) { + throw new RuntimeException(String.format("Could not create release %s for repository %s/%s", + release.getName(), repository.getOwner(), repository.getName()), ex); + } + } + + private static class AuthorizationInterceptor implements Interceptor { + private final String token; + + public AuthorizationInterceptor(String token) { + this.token = token; + } + + @Override + public 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/release/GitHubReleasePlugin.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java new file mode 100644 index 0000000000..ae2c44a769 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2021 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.release; + +import groovy.lang.MissingPropertyException; +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +/** + * @author Steve Riesenberg + */ +public class GitHubReleasePlugin implements Plugin { + @Override + public void apply(Project project) { + project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, new Action() { + @Override + public void execute(CreateGitHubReleaseTask createGitHubRelease) { + createGitHubRelease.setGroup("Release"); + createGitHubRelease.setDescription("Create a github release"); + createGitHubRelease.dependsOn("generateChangelog"); + + createGitHubRelease.setCreateRelease("true".equals(project.findProperty("createRelease"))); + createGitHubRelease.setVersion((String) project.findProperty("nextVersion")); + if (project.hasProperty("branch")) { + createGitHubRelease.setBranch((String) project.findProperty("branch")); + } + createGitHubRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); + if (createGitHubRelease.isCreateRelease() && createGitHubRelease.getGitHubAccessToken() == null) { + throw new MissingPropertyException("Please provide an access token with -PgitHubAccessToken=..."); + } + } + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/release/Release.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/Release.java new file mode 100644 index 0000000000..6dec2ceb79 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/Release.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2021 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.release; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Steve Riesenberg + */ +public class Release { + @SerializedName("tag_name") + private final String tag; + + @SerializedName("target_commitish") + private final String commit; + + @SerializedName("name") + private final String name; + + @SerializedName("body") + private final String body; + + @SerializedName("draft") + private final boolean draft; + + @SerializedName("prerelease") + private final boolean preRelease; + + @SerializedName("generate_release_notes") + private final boolean generateReleaseNotes; + + private Release(String tag, String commit, String name, String body, boolean draft, boolean preRelease, boolean generateReleaseNotes) { + this.tag = tag; + this.commit = commit; + this.name = name; + this.body = body; + this.draft = draft; + this.preRelease = preRelease; + this.generateReleaseNotes = generateReleaseNotes; + } + + public String getTag() { + return tag; + } + + public String getCommit() { + return commit; + } + + public String getName() { + return name; + } + + public String getBody() { + return body; + } + + public boolean isDraft() { + return draft; + } + + public boolean isPreRelease() { + return preRelease; + } + + public boolean isGenerateReleaseNotes() { + return generateReleaseNotes; + } + + @Override + public String toString() { + return "Release{" + + "tag='" + tag + '\'' + + ", commit='" + commit + '\'' + + ", name='" + name + '\'' + + ", body='" + body + '\'' + + ", draft=" + draft + + ", preRelease=" + preRelease + + ", generateReleaseNotes=" + generateReleaseNotes + + '}'; + } + + public static Builder tag(String tag) { + return new Builder().tag(tag); + } + + public static Builder commit(String commit) { + return new Builder().commit(commit); + } + + public static final class Builder { + private String tag; + private String commit; + private String name; + private String body; + private boolean draft; + private boolean preRelease; + private boolean generateReleaseNotes; + + private Builder() { + } + + public Builder tag(String tag) { + this.tag = tag; + return this; + } + + public Builder commit(String commit) { + this.commit = commit; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder body(String body) { + this.body = body; + return this; + } + + public Builder draft(boolean draft) { + this.draft = draft; + return this; + } + + public Builder preRelease(boolean preRelease) { + this.preRelease = preRelease; + return this; + } + + public Builder generateReleaseNotes(boolean generateReleaseNotes) { + this.generateReleaseNotes = generateReleaseNotes; + return this; + } + + public Release build() { + return new Release(tag, commit, name, body, draft, preRelease, generateReleaseNotes); + } + } +} 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 index 183cf09d5a..b9b0764ee5 100644 --- a/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java +++ b/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java @@ -1,15 +1,16 @@ package io.spring.gradle.github.milestones; +import java.util.concurrent.TimeUnit; + import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.gradle.github.milestones.GitHubMilestoneApi; -import org.springframework.gradle.github.milestones.RepositoryRef; -import java.util.concurrent.TimeUnit; +import org.springframework.gradle.github.RepositoryRef; +import org.springframework.gradle.github.milestones.GitHubMilestoneApi; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 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 index b4072c079e..0a1a293ab0 100644 --- a/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java +++ b/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java @@ -1,5 +1,7 @@ package org.springframework.gradle.github.milestones; +import java.util.concurrent.TimeUnit; + import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -7,7 +9,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.concurrent.TimeUnit; +import org.springframework.gradle.github.RepositoryRef; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java b/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java new file mode 100644 index 0000000000..6ac7955722 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2021 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.release; + +import java.util.concurrent.TimeUnit; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.gradle.github.RepositoryRef; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * @author Steve Riesenberg + */ +public class GitHubReleaseApiTests { + private GitHubReleaseApi github; + + private RepositoryRef repository = new RepositoryRef("spring-projects", "spring-security"); + + private MockWebServer server; + + private String baseUrl; + + @BeforeEach + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.github = new GitHubReleaseApi("mock-oauth-token"); + this.baseUrl = this.server.url("/api").toString(); + this.github.setBaseUrl(this.baseUrl); + } + + @AfterEach + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void publishReleaseWhenValidParametersThenSuccess() throws Exception { + String responseJson = "{\n" + + " \"url\": \"https://api.github.com/spring-projects/spring-security/releases/1\",\n" + + " \"html_url\": \"https://github.com/spring-projects/spring-security/releases/tags/v1.0.0\",\n" + + " \"assets_url\": \"https://api.github.com/spring-projects/spring-security/releases/1/assets\",\n" + + " \"upload_url\": \"https://uploads.github.com/spring-projects/spring-security/releases/1/assets{?name,label}\",\n" + + " \"tarball_url\": \"https://api.github.com/spring-projects/spring-security/tarball/v1.0.0\",\n" + + " \"zipball_url\": \"https://api.github.com/spring-projects/spring-security/zipball/v1.0.0\",\n" + + " \"discussion_url\": \"https://github.com/spring-projects/spring-security/discussions/90\",\n" + + " \"id\": 1,\n" + + " \"node_id\": \"MDc6UmVsZWFzZTE=\",\n" + + " \"tag_name\": \"v1.0.0\",\n" + + " \"target_commitish\": \"main\",\n" + + " \"name\": \"v1.0.0\",\n" + + " \"body\": \"Description of the release\",\n" + + " \"draft\": false,\n" + + " \"prerelease\": false,\n" + + " \"created_at\": \"2013-02-27T19:35:32Z\",\n" + + " \"published_at\": \"2013-02-27T19:35:32Z\",\n" + + " \"author\": {\n" + + " \"login\": \"sjohnr\",\n" + + " \"id\": 1,\n" + + " \"node_id\": \"MDQ6VXNlcjE=\",\n" + + " \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" + + " \"gravatar_id\": \"\",\n" + + " \"url\": \"https://api.github.com/users/sjohnr\",\n" + + " \"html_url\": \"https://github.com/sjohnr\",\n" + + " \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" + + " \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" + + " \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" + + " \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" + + " \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" + + " \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" + + " \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" + + " \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" + + " \"type\": \"User\",\n" + + " \"site_admin\": false\n" + + " },\n" + + " \"assets\": [\n" + + " {\n" + + " \"url\": \"https://api.github.com/spring-projects/spring-security/releases/assets/1\",\n" + + " \"browser_download_url\": \"https://github.com/spring-projects/spring-security/releases/download/v1.0.0/example.zip\",\n" + + " \"id\": 1,\n" + + " \"node_id\": \"MDEyOlJlbGVhc2VBc3NldDE=\",\n" + + " \"name\": \"example.zip\",\n" + + " \"label\": \"short description\",\n" + + " \"state\": \"uploaded\",\n" + + " \"content_type\": \"application/zip\",\n" + + " \"size\": 1024,\n" + + " \"download_count\": 42,\n" + + " \"created_at\": \"2013-02-27T19:35:32Z\",\n" + + " \"updated_at\": \"2013-02-27T19:35:32Z\",\n" + + " \"uploader\": {\n" + + " \"login\": \"sjohnr\",\n" + + " \"id\": 1,\n" + + " \"node_id\": \"MDQ6VXNlcjE=\",\n" + + " \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" + + " \"gravatar_id\": \"\",\n" + + " \"url\": \"https://api.github.com/users/sjohnr\",\n" + + " \"html_url\": \"https://github.com/sjohnr\",\n" + + " \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" + + " \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" + + " \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" + + " \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" + + " \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" + + " \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" + + " \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" + + " \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" + + " \"type\": \"User\",\n" + + " \"site_admin\": false\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + this.github.publishRelease(this.repository, Release.tag("1.0.0").build()); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases"); + assertThat(recordedRequest.getBody().toString()).isEqualTo("{\"tag_name\":\"1.0.0\"}"); + } + + @Test + public void publishReleaseWhenErrorResponseThenException() throws Exception { + this.server.enqueue(new MockResponse().setResponseCode(400)); + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> this.github.publishRelease(this.repository, Release.tag("1.0.0").build())); + } +}