You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2019/04/24 12:32:08 UTC
[sling-org-apache-sling-committer-cli] 39/44: SLING-8337 - Create
sub-command to manage the Jira update when promoting a release
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git
commit 28c81f3cc96b0f1aef38f48f2b5e924b4493874f
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 17:53:34 2019 +0300
SLING-8337 - Create sub-command to manage the Jira update when promoting a release
Implement proper error handling in the VersionClient and MockJira.
---
.../apache/sling/cli/impl/jira/ErrorResponse.java | 36 ++++
.../apache/sling/cli/impl/jira/VersionClient.java | 42 +++--
.../org/apache/sling/cli/impl/jira/MockJira.java | 182 ++++++++++++++++++---
.../sling/cli/impl/jira/VersionClientTest.java | 15 +-
4 files changed, 242 insertions(+), 33 deletions(-)
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/ErrorResponse.java b/src/main/java/org/apache/sling/cli/impl/jira/ErrorResponse.java
new file mode 100644
index 0000000..a0dc3eb
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/jira/ErrorResponse.java
@@ -0,0 +1,36 @@
+/*
+ * 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 org.apache.sling.cli.impl.jira;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class ErrorResponse {
+
+ private List<String> errorMessages = new ArrayList<>();
+ private Map<String, String> errors = new HashMap<>();
+
+ public List<String> getErrorMessages() {
+ return errorMessages;
+ }
+
+ public Map<String, String> getErrors() {
+ return errors;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 39419a9..8db031f 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -16,7 +16,6 @@
*/
package org.apache.sling.cli.impl.jira;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -39,6 +38,8 @@ import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonWriter;
@@ -138,20 +139,38 @@ public class VersionClient {
InputStreamReader reader = new InputStreamReader(content)) {
if (response.getStatusLine().getStatusCode() != 201) {
- // TODO - try and parse JSON error message, fall back to status code
- try ( BufferedReader bufferedReader = new BufferedReader(reader)) {
- String line;
- while ( (line = bufferedReader.readLine()) != null )
- System.out.println(line);
- }
-
- throw new IOException("Status line : " + response.getStatusLine());
+ throw newException(response, reader);
}
}
}
}
}
+ private IOException newException(CloseableHttpResponse response, InputStreamReader reader) throws IOException {
+
+ StringBuilder message = new StringBuilder();
+ message.append("Status line : " + response.getStatusLine());
+
+ try {
+ Gson gson = new Gson();
+ ErrorResponse errors = gson.fromJson(reader, ErrorResponse.class);
+ if ( !errors.getErrorMessages().isEmpty() )
+ message.append(". Error messages: ")
+ .append(errors.getErrorMessages());
+
+ if ( !errors.getErrors().isEmpty() )
+ errors.getErrors().entrySet().stream()
+ .forEach( e -> message.append(". Error for " + e.getKey() + " : " + e.getValue()));
+
+ } catch ( JsonIOException | JsonSyntaxException e) {
+ message.append(". Failed parsing response as JSON ( ")
+ .append(e.getMessage())
+ .append(" )");
+ }
+
+ return new IOException(message.toString());
+ }
+
private Optional<Version> findVersion(Predicate<Version> matcher, CloseableHttpClient client) throws IOException {
HttpGet get = newGet("project/SLING/versions");
@@ -159,7 +178,7 @@ public class VersionClient {
try (InputStream content = response.getEntity().getContent();
InputStreamReader reader = new InputStreamReader(content)) {
if (response.getStatusLine().getStatusCode() != 200)
- throw new IOException("Status line : " + response.getStatusLine());
+ throw newException(response, reader);
Gson gson = new Gson();
Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
@@ -186,7 +205,8 @@ public class VersionClient {
try (InputStream content = response.getEntity().getContent();
InputStreamReader reader = new InputStreamReader(content)) {
if (response.getStatusLine().getStatusCode() != 200)
- throw new IOException("Status line : " + response.getStatusLine());
+ throw newException(response, reader);
+
Gson gson = new Gson();
VersionRelatedIssuesCount issuesCount = gson.fromJson(reader, VersionRelatedIssuesCount.class);
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
index 903f8b0..3df51ca 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
@@ -16,21 +16,35 @@
*/
package org.apache.sling.cli.impl.jira;
+import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.junit.rules.ExternalResource;
+import com.google.gson.Gson;
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.BasicAuthenticator;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpPrincipal;
import com.sun.net.httpserver.HttpServer;
public class MockJira extends ExternalResource {
private static final Pattern VERSION_RELATED_ISSUES = Pattern.compile("^/jira/rest/api/2/version/(\\d+)/relatedIssueCounts$");
+ static final String AUTH_USER = "jira-user";
+ static final String AUTH_PWD = "jira-password";
+
public static void main(String[] args) throws Throwable {
MockJira mj = new MockJira();
@@ -43,35 +57,103 @@ public class MockJira extends ExternalResource {
@Override
protected void before() throws Throwable {
server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
- server.createContext("/", httpExchange -> {
+ HttpContext rootContext = server.createContext("/");
+ rootContext.setAuthenticator(new BasicAuthenticator(getClass().getSimpleName()) {
- if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/project/SLING/versions") ) {
- httpExchange.sendResponseHeaders(200, 0);
- try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
- OutputStream out = httpExchange.getResponseBody() ) {
- IOUtils.copy(in, out);
- }
- } else {
- Matcher matcher = VERSION_RELATED_ISSUES.matcher(httpExchange.getRequestURI().getPath());
- if ( matcher.matches() ) {
- int version = Integer.parseInt(matcher.group(1));
- InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
- if ( in == null ) {
- httpExchange.sendResponseHeaders(404, -1);
- } else {
- httpExchange.sendResponseHeaders(200, 0);
- try ( OutputStream out = httpExchange.getResponseBody() ) {
- IOUtils.copy(in, out);
- }
+ @Override
+ public boolean checkCredentials(String username, String password) {
+ return AUTH_USER.equals(username) && AUTH_PWD.equals(password);
+ }
+
+ @Override
+ public Result authenticate(HttpExchange t) {
+ if ( t.getRequestMethod().contentEquals("GET") )
+ return new Authenticator.Success(new HttpPrincipal("anonymous", getClass().getSimpleName()));
+ return super.authenticate(t);
+ }
+ });
+ rootContext.setHandler(httpExchange -> {
+
+ switch ( httpExchange.getRequestMethod() ) {
+ case "GET":
+ if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/project/SLING/versions") ) {
+ httpExchange.sendResponseHeaders(200, 0);
+ try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
+ OutputStream out = httpExchange.getResponseBody() ) {
+ IOUtils.copy(in, out);
}
} else {
- httpExchange.sendResponseHeaders(400, -1);
+ Matcher matcher = VERSION_RELATED_ISSUES.matcher(httpExchange.getRequestURI().getPath());
+ if ( matcher.matches() ) {
+ int version = Integer.parseInt(matcher.group(1));
+ InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
+ if ( in == null ) {
+ httpExchange.sendResponseHeaders(404, -1);
+ } else {
+ httpExchange.sendResponseHeaders(200, 0);
+ try ( OutputStream out = httpExchange.getResponseBody() ) {
+ IOUtils.copy(in, out);
+ }
+ }
+ } else {
+ httpExchange.sendResponseHeaders(400, -1);
+ }
+ }
+ break;
+ case "POST":
+ if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/version") ) {
+ handleCreateVersion(httpExchange);
+ return;
}
+ httpExchange.sendResponseHeaders(400, -1);
+ break;
+ default:
+ httpExchange.sendResponseHeaders(400, -1);
}
+
});
server.start();
}
+
+ private void handleCreateVersion(HttpExchange httpExchange) throws IOException {
+ Gson gson = new Gson();
+ try ( InputStreamReader reader = new InputStreamReader(httpExchange.getRequestBody())) {
+ VersionToCreate version = gson.fromJson(reader, VersionToCreate.class);
+ if ( version.getName() == null || version.getName().isEmpty() ) {
+ error(httpExchange, gson,
+ er -> er.getErrors().put("name", "You must specify a valid version name"));
+ return;
+ }
+
+ if ( !"SLING".equals(version.getProject()) ) {
+ error(httpExchange, gson,
+ er -> er.getErrorMessages().add("Project must be specified to create a version."));
+ return;
+ }
+
+ // note not all fields are sent, projectId and self are missing
+ CreatedVersion createdVersion = new CreatedVersion();
+ createdVersion.archived = false;
+ createdVersion.id = ThreadLocalRandom.current().nextInt();
+ createdVersion.released = false;
+ createdVersion.name = version.getName();
+
+ try ( OutputStreamWriter out = new OutputStreamWriter(httpExchange.getResponseBody()) ) {
+ httpExchange.sendResponseHeaders(201, 0);
+ gson.toJson(createdVersion, out);
+ }
+ }
+ }
+
+ private void error(HttpExchange httpExchange, Gson gson, Consumer<ErrorResponse> c) throws IOException {
+ try ( OutputStreamWriter out = new OutputStreamWriter(httpExchange.getResponseBody()) ) {
+ httpExchange.sendResponseHeaders(400, 0);
+ ErrorResponse er = new ErrorResponse();
+ c.accept(er);
+ gson.toJson(er, out);
+ }
+ }
@Override
protected void after() {
@@ -83,4 +165,64 @@ public class MockJira extends ExternalResource {
return server.getAddress().getPort();
}
+
+ static class VersionToCreate {
+ private String name;
+ private String project;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getProject() {
+ return project;
+ }
+
+ public void setProject(String project) {
+ this.project = project;
+ }
+ }
+
+ static class CreatedVersion {
+ private String name;
+ private int id;
+ private boolean archived;
+ private boolean released;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public boolean isArchived() {
+ return archived;
+ }
+
+ public void setArchived(boolean archived) {
+ this.archived = archived;
+ }
+
+ public boolean isReleased() {
+ return released;
+ }
+
+ public void setReleased(boolean released) {
+ this.released = released;
+ }
+ }
}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
index 0d67dee..2951b3b 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
@@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
+import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -38,8 +39,8 @@ public class VersionClientTest {
static {
SYSTEM_PROPS.put("asf.username", "asf-user");
SYSTEM_PROPS.put("asf.password", "asf-password");
- SYSTEM_PROPS.put("jira.username", "jira-user");
- SYSTEM_PROPS.put("jira.password", "jira-password");
+ SYSTEM_PROPS.put("jira.username", MockJira.AUTH_USER);
+ SYSTEM_PROPS.put("jira.password", MockJira.AUTH_PWD);
}
@Rule
@@ -92,4 +93,14 @@ public class VersionClientTest {
assertThat("successor", successor, nullValue());
}
+
+ @Test
+ public void createVersion() throws IOException {
+ versionClient.create("XSS Protection API 2.0.10");
+ }
+
+ @Test(expected = IOException.class)
+ public void illegalVersionFails() throws IOException {
+ versionClient.create("");
+ }
}