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 10:48:44 UTC

[sling-org-apache-sling-committer-cli] 34/42: 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 a487fa59543eff10bdadb8611b95eeecc1e53070
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 14:10:42 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Add credentials for Jira access as well.
---
 pom.xml                                            | 28 ++++++++++
 .../apache/sling/cli/impl/CredentialsService.java  | 59 ++++++++++++++--------
 .../sling/cli/impl/http/HttpClientFactory.java     | 52 +++++++++++++++++++
 .../apache/sling/cli/impl/jira/VersionClient.java  | 12 +++--
 .../org/apache/sling/cli/impl/mail/Mailer.java     |  2 +-
 .../cli/impl/nexus/StagingRepositoryFinder.java    | 24 ++-------
 .../sling/cli/impl/people/MembersFinder.java       |  2 +-
 .../cli/impl/release/UpdateReporterCommand.java    | 23 ++-------
 src/main/resources/scripts/launcher.sh             |  4 +-
 .../sling/cli/impl/CredentialsServiceTest.java     | 56 ++++++++++++++++++++
 .../sling/cli/impl/jira/SystemPropertiesRule.java  | 51 +++++++++++++++++++
 ...rsionFinderTest.java => VersionClientTest.java} | 48 +++++++++++++++---
 12 files changed, 285 insertions(+), 76 deletions(-)

diff --git a/pom.xml b/pom.xml
index f76647e..f820ac0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -198,5 +198,33 @@
             <version>1.3</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock.junit4</artifactId>
+            <version>2.4.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock.junit4</artifactId>
+            <version>2.4.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.event</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.log</artifactId>
+            <version>1.3.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/cli/impl/CredentialsService.java b/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
index 3ddbcca..127c8fe 100644
--- a/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
+++ b/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
@@ -26,30 +26,49 @@ import org.osgi.service.component.annotations.Component;
 @Component(service = CredentialsService.class)
 public class CredentialsService {
 
-    private static final String USER_SYS_PROP = "asf.username";
-    private static final String PASSWORD_SYS_PROP = "asf.password";
-    private static final String USER_ENV_PROP = "ASF_USERNAME";
-    private static final String PASSWORD_ENV_PROP = "ASF_PASSWORD";
+    private static final ValueSource ASF_USER = new ValueSource("asf.username", "ASF_USERNAME", "ASF user information");
+    private static final ValueSource ASF_PWD = new ValueSource("asf.password", "ASF_PASSWORD", "ASF password");
 
-    private volatile Credentials credentials;
+    private static final ValueSource JIRA_USER = new ValueSource("jira.username", "JIRA_USERNAME", "Jira user information");
+    private static final ValueSource JIRA_PWD = new ValueSource("jira.password", "JIRA_PASSWORD", "Jira password");
+    
+    private Credentials asfCredentials;
+    private Credentials jiraCredentials;
 
     @Activate
-    private void activate() {
-        Optional<String> username =
-                Optional.ofNullable(System.getProperty(USER_SYS_PROP)).or(() -> Optional.ofNullable(System.getenv(USER_ENV_PROP)));
-        Optional<String> password =
-                Optional.ofNullable(System.getProperty(PASSWORD_SYS_PROP)).or(() -> Optional.ofNullable(System.getenv(PASSWORD_ENV_PROP)));
-        credentials = new Credentials(
-                username.orElseThrow(() -> new IllegalStateException(
-                        String.format("Cannot detect user information after looking for %s system property and %s environment variable.",
-                                USER_SYS_PROP, USER_ENV_PROP))),
-                password.orElseThrow(() -> new IllegalStateException(
-                        String.format("Cannot detect password after looking for %s system property and %s environment variable.",
-                                PASSWORD_SYS_PROP, PASSWORD_ENV_PROP)))
-        );
+    protected void activate() {
+        asfCredentials = new Credentials(ASF_USER.getValue(), ASF_PWD.getValue());
+        jiraCredentials = new Credentials(JIRA_USER.getValue(), JIRA_PWD.getValue());
     }
+    
+    public Credentials getAsfCredentials() {
+        return asfCredentials;
+    }
+    
+    public Credentials getJiraCredentials() {
+        return jiraCredentials;
+    }
+
+    static class ValueSource {
+
+        private final String sysProp;
+        private final String envVar;
+        private final String friendlyName;
+        
+        public ValueSource(String sysProp, String envVar, String friendlyName) {
 
-    public Credentials getCredentials() {
-        return credentials;
+            this.sysProp = sysProp;
+            this.envVar = envVar;
+            this.friendlyName = friendlyName;
+        }
+        
+        public String getValue() {
+            
+            return Optional.ofNullable(System.getProperty(sysProp))
+                    .or( () -> Optional.ofNullable(System.getenv(envVar)) )
+                    .orElseThrow(() -> new IllegalStateException(String.format("Cannot detect %s after looking for %s system property and %s environment variable.", 
+                            friendlyName, sysProp, envVar)));
+                    
+        }   
     }
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java b/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java
new file mode 100644
index 0000000..7975145
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.http;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.Credentials;
+import org.apache.sling.cli.impl.CredentialsService;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(service = HttpClientFactory.class)
+public class HttpClientFactory {
+    
+    @Reference
+    private CredentialsService credentialsService;
+
+    public CloseableHttpClient newClient() {
+        
+        Credentials asf = credentialsService.getAsfCredentials();
+        Credentials jira = credentialsService.getJiraCredentials();
+        
+        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+        credentialsProvider.setCredentials(new AuthScope("repository.apache.org", 443), 
+                new UsernamePasswordCredentials(asf.getUsername(), asf.getPassword()));
+        credentialsProvider.setCredentials(new AuthScope("reporter.apache.org", 443), 
+                new UsernamePasswordCredentials(asf.getUsername(), asf.getPassword()));
+        credentialsProvider.setCredentials(new AuthScope("jira.apache.org", 443), 
+                new UsernamePasswordCredentials(jira.getUsername(), jira.getPassword()));
+        
+        return HttpClients.custom()
+                .setDefaultCredentialsProvider(credentialsProvider)
+                .build();
+    }
+}
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 6a4a770..77e6881 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
@@ -32,9 +32,10 @@ import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.release.Release;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
@@ -48,6 +49,9 @@ public class VersionClient {
     
     private static final String JIRA_URL_PREFIX = "https://issues.apache.org/jira/rest/api/2/";
     private static final String CONTENT_TYPE_JSON = "application/json";
+    
+    @Reference
+    private HttpClientFactory httpClientFactory;
 
     /**
      * Finds a Jira version which matches the specified release
@@ -59,7 +63,7 @@ public class VersionClient {
     public Version find(Release release) {
         Version version;
         
-        try (CloseableHttpClient client = HttpClients.createDefault()) {
+        try (CloseableHttpClient client = httpClientFactory.newClient()) {
             version = findVersion( v -> release.getName().equals(v.getName()), client)
                     .orElseThrow( () -> new IllegalArgumentException("No version found with name " + release.getName()));
             populateRelatedIssuesCount(client, version);
@@ -92,7 +96,7 @@ public class VersionClient {
     public Version findSuccessorVersion(Release release) {
         Version version;
         
-        try ( CloseableHttpClient client = HttpClients.createDefault() ) {
+        try (CloseableHttpClient client = httpClientFactory.newClient()) {
             Optional<Version> opt = findVersion ( 
                     v -> {
                         Release releaseFromVersion = Release.fromString(v.getName()).get(0);
@@ -125,7 +129,7 @@ public class VersionClient {
         post.addHeader("Accept", CONTENT_TYPE_JSON);
         post.setEntity(new StringEntity(w.toString()));
 
-        try (CloseableHttpClient client = HttpClients.createDefault() ) {
+        try (CloseableHttpClient client = httpClientFactory.newClient()) {
             try (CloseableHttpResponse response = client.execute(post)) {
                 try (InputStream content = response.getEntity().getContent();
                         InputStreamReader reader = new InputStreamReader(content)) {
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
index 0c0a4e4..e7749d9 100644
--- a/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
+++ b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
@@ -66,7 +66,7 @@ public class Mailer {
         try {
             MimeMessage message = new MimeMessage(session);
             Member sender = membersFinder.getCurrentMember();
-            Credentials credentials = credentialsService.getCredentials();
+            Credentials credentials = credentialsService.getAsfCredentials();
             message.setFrom(new InternetAddress(sender.getEmail(), sender.getEmail(), StandardCharsets.UTF_8.name()));
             message.setSubject(subject);
             message.setText(body, StandardCharsets.UTF_8.name());
diff --git a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
index 9093a04..1926181 100644
--- a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
@@ -23,17 +23,11 @@ import java.util.List;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.sling.cli.impl.Credentials;
-import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.nexus.StagingRepository.Status;
-import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
@@ -45,18 +39,8 @@ public class StagingRepositoryFinder {
     private static final String REPOSITORY_PREFIX = "orgapachesling-";
 
     @Reference
-    private CredentialsService credentialsService;
+    private HttpClientFactory httpClientFactory;
 
-    private BasicCredentialsProvider credentialsProvider;
-    
-    @Activate
-    private void activate() {
-        Credentials credentials = credentialsService.getCredentials();
-        credentialsProvider = new BasicCredentialsProvider();
-        credentialsProvider.setCredentials(new AuthScope("repository.apache.org", 443), 
-                new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword()));
-    }
-    
     public List<StagingRepository> list() throws IOException {
         return this.withStagingRepositories( reader -> {
             Gson gson = new Gson();
@@ -80,9 +64,7 @@ public class StagingRepositoryFinder {
     }
     
     private <T> T withStagingRepositories(Function<InputStreamReader, T> function) throws IOException {
-        try ( CloseableHttpClient client = HttpClients.custom()
-                .setDefaultCredentialsProvider(credentialsProvider)
-                .build() ) {
+        try ( CloseableHttpClient client = httpClientFactory.newClient() ) {
             HttpGet get = new HttpGet("https://repository.apache.org/service/local/staging/profile_repositories");
             get.addHeader("Accept", "application/json");
             try ( CloseableHttpResponse response = client.execute(get)) {
diff --git a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
index 70b5469..fc15455 100644
--- a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
@@ -113,7 +113,7 @@ public class MembersFinder {
     }
 
     public Member getCurrentMember() {
-         return getMemberById(credentialsService.getCredentials().getUsername());
+         return getMemberById(credentialsService.getAsfCredentials().getUsername());
     }
 
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
index fdf0ef2..1042c5c 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
@@ -26,22 +26,15 @@ import java.util.Date;
 import java.util.List;
 
 import org.apache.http.NameValuePair;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
 import org.apache.http.message.BasicNameValuePair;
 import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.Credentials;
-import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
 import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
-import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -62,17 +55,15 @@ public class UpdateReporterCommand implements Command {
     private StagingRepositoryFinder repoFinder;
 
     @Reference
-    private CredentialsService credentialsService;
+    private HttpClientFactory httpClientFactory;
 
-    private CredentialsProvider credentialsProvider;
 
     @Override
     public void execute(String target) {
         try {
             StagingRepository repository = repoFinder.find(Integer.parseInt(target));
             
-            try (CloseableHttpClient client =
-                         HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).build()) {
+            try (CloseableHttpClient client = httpClientFactory.newClient() ) {
                 for ( Release release : Release.fromString(repository.getDescription()) ) {
                     HttpPost post = new HttpPost("https://reporter.apache.org/addrelease.py");
                     SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
@@ -96,12 +87,4 @@ public class UpdateReporterCommand implements Command {
         }
 
     }
-
-    @Activate
-    private void activate() {
-        Credentials credentials = credentialsService.getCredentials();
-        credentialsProvider = new BasicCredentialsProvider();
-        credentialsProvider.setCredentials(new AuthScope("reporter.apache.org", 443),
-                new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword()));
-    }
 }
diff --git a/src/main/resources/scripts/launcher.sh b/src/main/resources/scripts/launcher.sh
index 08f103f..e5094b5 100755
--- a/src/main/resources/scripts/launcher.sh
+++ b/src/main/resources/scripts/launcher.sh
@@ -31,4 +31,6 @@ exec /opt/jre/bin/java \
     -c /usr/share/sling-cli/artifacts \
     -D "$ARGS_PROP" \
     -V "asf.username=${ASF_USERNAME}" \
-    -V "asf.password=${ASF_PASSWORD}"
+    -V "asf.password=${ASF_PASSWORD}" \
+    -V "jira.username=${JIRA_USERNAME}" \
+    -V "jira.password=${JIRA_PASSWORD}"
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/cli/impl/CredentialsServiceTest.java b/src/test/java/org/apache/sling/cli/impl/CredentialsServiceTest.java
new file mode 100644
index 0000000..571adba
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/CredentialsServiceTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class CredentialsServiceTest {
+
+    private static final String[] VALID_PROPS = new String[] { "asf.username", "asf.password", "jira.username", "jira.password" };
+
+    @Test(expected = IllegalStateException.class)
+    public void noCredentialSourcesFound() {
+        new CredentialsService().activate();
+    }
+    
+    @Test
+    public void credentialsFromSystemProps() {
+        for ( String prop : VALID_PROPS ) {
+            System.setProperty(prop, prop + ".val");
+        }
+        
+        try {
+            
+            CredentialsService creds = new CredentialsService();
+            creds.activate();
+            
+            assertThat(creds.getAsfCredentials().getUsername(), equalTo("asf.username.val"));
+            assertThat(creds.getAsfCredentials().getPassword(), equalTo("asf.password.val"));
+            
+            assertThat(creds.getJiraCredentials().getUsername(), equalTo("jira.username.val"));
+            assertThat(creds.getJiraCredentials().getPassword(), equalTo("jira.password.val"));
+            
+        } finally {
+            for ( String prop : VALID_PROPS ) {
+                System.clearProperty(prop);
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/SystemPropertiesRule.java b/src/test/java/org/apache/sling/cli/impl/jira/SystemPropertiesRule.java
new file mode 100644
index 0000000..c6273b5
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/SystemPropertiesRule.java
@@ -0,0 +1,51 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+import org.junit.rules.ExternalResource;
+
+public class SystemPropertiesRule extends ExternalResource {
+
+    private final Map<String, String> propsToRestore = new HashMap<String, String>();
+    private final Map<String, String> propsToOverride;
+    
+    public SystemPropertiesRule(Map<String, String> propsToOverride) {
+        this.propsToOverride = propsToOverride;
+    }
+    
+    @Override
+    protected void before() throws Throwable {
+        for (Map.Entry<String, String> prop : propsToOverride.entrySet() )
+            propsToRestore.put(prop.getKey(), set(prop));
+    }
+
+    private String set(Map.Entry<String, String> prop) {
+        if ( prop.getValue() != null )
+            return System.setProperty(prop.getKey(), prop.getValue());
+        
+        return System.clearProperty(prop.getKey());
+    }
+    
+    @Override
+    protected void after() {
+        for (Map.Entry<String, String> prop : propsToRestore.entrySet() )
+            set(prop);
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
similarity index 63%
rename from src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
rename to src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
index 6f11f6c..0cac72d 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
@@ -24,21 +24,53 @@ import static org.junit.Assert.assertThat;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.function.Function;
 
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.release.Release;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
-public class VersionFinderTest {
+public class VersionClientTest {
 
-    private VersionClient finder = new StubVersionFinder();
+    private static final Map<String, String> SYSTEM_PROPS = new HashMap<>();
+    static {
+        SYSTEM_PROPS.put("asf.username", "user");
+        SYSTEM_PROPS.put("asf.password", "password");
+        SYSTEM_PROPS.put("jira.username", "user");
+        SYSTEM_PROPS.put("jira.password", "password");
+    }
+    
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+    
+    @Rule
+    public final SystemPropertiesRule sysProps = new SystemPropertiesRule(SYSTEM_PROPS);
+    
+    private VersionClient versionClient;
+    
+    @Before
+    public void prepareDependencies() throws ReflectiveOperationException {
+        context.registerInjectActivateService(new CredentialsService());
+        context.registerInjectActivateService(new HttpClientFactory());
+        
+        versionClient = new StubVersionClient();
+        Field factoryField = VersionClient.class.getDeclaredField("httpClientFactory");
+        factoryField.setAccessible(true);
+        factoryField.set(versionClient, context.getService(HttpClientFactory.class));
+    }
     
     @Test
     public void findMatchingVersion() {
         
-        finder = new StubVersionFinder();
-        Version version = finder.find(Release.fromString("XSS Protection API 1.0.2").get(0));
+        Version version = versionClient.find(Release.fromString("XSS Protection API 1.0.2").get(0));
         
         assertThat("version", version, notNullValue());
         assertThat("version.name", version.getName(), equalTo("XSS Protection API 1.0.2"));
@@ -49,12 +81,12 @@ public class VersionFinderTest {
     @Test(expected = IllegalArgumentException.class)
     public void missingVersionNotFound() {
         
-        finder.find(Release.fromString("XSS Protection API 1.0.3").get(0));
+        versionClient.find(Release.fromString("XSS Protection API 1.0.3").get(0));
     }
     
     @Test
     public void findSuccessorVersion() {
-        Version successor = finder.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.2").get(0));
+        Version successor = versionClient.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.2").get(0));
         
         assertThat("successor", successor, notNullValue());
         assertThat("successor.name", successor.getName(), equalTo("XSS Protection API 1.0.4"));
@@ -62,12 +94,12 @@ public class VersionFinderTest {
 
     @Test
     public void noSuccessorVersion() {
-        Version successor = finder.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.16").get(0));
+        Version successor = versionClient.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.16").get(0));
         
         assertThat("successor", successor, nullValue());
     }
     
-    private static final class StubVersionFinder extends VersionClient {
+    private static final class StubVersionClient extends VersionClient {
         @Override
         protected <T> T doWithJiraVersions(CloseableHttpClient client, Function<InputStreamReader, T> parserCallback)
                 throws IOException {