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/23 14:54:48 UTC

[sling-org-apache-sling-committer-cli] 39/39: 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("");
+    }
 }