/*
* Copyright 2013-2014 Urs Wolfer
*
* 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
*
* 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 com.urswolfer.gerrit.client.rest.http;
import com.google.common.base.Charsets;
import com.google.common.truth.Truth;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.urswolfer.gerrit.client.rest.GerritAuthData;
import com.urswolfer.gerrit.client.rest.GerritRestApi;
import com.urswolfer.gerrit.client.rest.GerritRestApiFactory;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.resource.FileResource;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.List;
/**
* @author Urs Wolfer
*/
public class GerritRestClientTest {
private String jettyUrl;
@BeforeClass
public void startJetty() throws Exception {
Server server = new Server(0);
ResourceHandler resourceHandler = new ResourceHandler();
MimeTypes mimeTypes = new MimeTypes();
mimeTypes.addMimeMapping("json", "application/json");
resourceHandler.setMimeTypes(mimeTypes);
URL url = this.getClass().getResource(".");
resourceHandler.setBaseResource(new FileResource(url));
resourceHandler.setWelcomeFiles(new String[] {"changes.json", "projects.json", "account.json"});
ServletContextHandler servletContextHandler = new ServletContextHandler();
servletContextHandler.addServlet(LoginSimulationServlet.class, "/login/");
ServletContextHandler basicAuthContextHandler = new ServletContextHandler(ServletContextHandler.SECURITY);
basicAuthContextHandler.setSecurityHandler(basicAuth("foo", "bar", "Gerrit Auth"));
basicAuthContextHandler.setContextPath("/a");
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] {
servletContextHandler,
resourceHandler,
basicAuthContextHandler
});
server.setHandler(handlers);
server.start();
Connector connector = server.getConnectors()[0];
String host = "localhost";
int port = connector.getLocalPort();
jettyUrl = String.format("http://%s:%s", host, port);
}
private static SecurityHandler basicAuth(String username, String password, String realm) {
HashLoginService loginService = new HashLoginService();
loginService.putUser(username, Credential.getCredential(password), new String[]{"user"});
loginService.setName(realm);
Constraint constraint = new Constraint();
constraint.setName(Constraint.__DIGEST_AUTH);
constraint.setRoles(new String[]{"user"});
constraint.setAuthenticate(true);
ConstraintMapping constraintMapping = new ConstraintMapping();
constraintMapping.setConstraint(constraint);
constraintMapping.setPathSpec("/*");
ConstraintSecurityHandler csh = new ConstraintSecurityHandler();
csh.setAuthenticator(new BasicAuthenticator());
csh.setRealmName("realm");
csh.addConstraintMapping(constraintMapping);
csh.setLoginService(loginService);
return csh;
}
@Test(expectedExceptions = RestApiException.class)
public void testInvalidHost() throws Exception {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritAuthData.Basic authData = new GerritAuthData.Basic("http://averyinvaliddomainforgerritresttest.com:8089");
GerritApi gerritClient = gerritRestApiFactory.create(authData);
gerritClient.changes().query().get();
}
private GerritRestApi getGerritApiWithJettyHost() {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
return gerritRestApiFactory.create(new GerritAuthData.Basic(jettyUrl));
}
@Test
public void testGetChanges() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
List<ChangeInfo> changes = gerritClient.changes().query().get();
Truth.assertThat(changes.size()).isEqualTo(3);
}
@Test
public void testGetSelfAccount() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
AccountInfo accountInfo = gerritClient.accounts().self().get();
Truth.assertThat(accountInfo.name).isEqualTo("John Doe");
}
@Test(expectedExceptions = HttpStatusException.class)
public void testGetInvalidAccount() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
gerritClient.accounts().id("invalid").get();
}
@Test
public void testGetProjects() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
List<ProjectInfo> projects = gerritClient.projects().list().get();
Truth.assertThat(projects.size()).isEqualTo(3);
}
@Test
public void testGetCommitMsgHook() throws Exception {
GerritRestApi gerritClient = getGerritApiWithJettyHost();
InputStream commitMessageHook = gerritClient.tools().getCommitMessageHook();
String result = new BufferedReader(new InputStreamReader(commitMessageHook, Charsets.UTF_8)).readLine();
Truth.assertThat(result).isEqualTo("dummy-commit-msg-hook");
}
@Test(expectedExceptions = HttpStatusException.class)
public void testStarNotLoggedIn() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
gerritClient.accounts().self().starChange("1");
}
@Test(expectedExceptions = HttpStatusException.class)
public void testUnstarNotLoggedIn() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
gerritClient.accounts().self().unstarChange("1");
}
@Test(expectedExceptions = HttpStatusException.class)
public void testAbandonNotLoggedIn() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
gerritClient.changes().id(1).abandon();
}
@Test(expectedExceptions = RestApiException.class)
public void testInvalidJson() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
gerritClient.accounts().id("invalid_json").get();
}
@Test(expectedExceptions = RestApiException.class)
public void testNullJson() throws Exception {
GerritApi gerritClient = getGerritApiWithJettyHost();
gerritClient.accounts().id("null_json").get();
}
@Test(expectedExceptions = IllegalStateException.class)
public void testUnsupportedHttpMethod() throws Exception {
GerritRestClient gerritRestClient = new GerritRestClient(
new GerritAuthData.Basic(jettyUrl), new HttpRequestExecutor());
gerritRestClient.requestRest("/invalid/", null, GerritRestClient.HttpVerb.HEAD);
}
/**
* Tests authentication with a login which us handled by HTTP auth (preemptive authentication is assumed)
* (path: "/a/changes/" isn't mapped -> status 404).
*/
@Test
public void testUserAuth() throws Exception {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritApi gerritClient = gerritRestApiFactory.create(new GerritAuthData.Basic(jettyUrl, "foo", "bar"));
boolean catched = false;
try {
gerritClient.changes().query().get();
} catch (HttpStatusException e) {
catched = true;
// 404 because this url does not provide a valid response (not set up in this test case)
Truth.assertThat(e.getStatusCode()).isEqualTo(404);
}
Truth.assertThat(catched).isTrue();
}
/**
* Tests authentication with an invalid HTTP login (preemptive authentication is assumed). Status 401 expected.
*/
@Test
public void testInvalidUserAuth() throws Exception {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritApi gerritClient = gerritRestApiFactory.create(new GerritAuthData.Basic(jettyUrl, "foox", "bar"));
boolean catched = false;
try {
gerritClient.changes().query().get();
} catch (HttpStatusException e) {
catched = true;
Truth.assertThat(e.getStatusCode()).isEqualTo(401);
Truth.assertThat(e.getStatusText().toLowerCase()).contains("unauthorized");
}
Truth.assertThat(catched).isTrue();
}
/**
* Tests that client-builder-extensions are called correctly.
*/
@Test
public void testHttpClientBuilderExtension() throws Exception {
final boolean[] extendCalled = {false};
final boolean[] extendCredentialProviderCalled = {false};
HttpClientBuilderExtension httpClientBuilderExtension = new HttpClientBuilderExtension() {
@Override
public HttpClientBuilder extend(HttpClientBuilder httpClientBuilder, GerritAuthData authData) {
extendCalled[0] = true;
return super.extend(httpClientBuilder, authData);
}
@Override
public CredentialsProvider extendCredentialProvider(HttpClientBuilder httpClientBuilder, CredentialsProvider credentialsProvider, GerritAuthData authData) {
extendCredentialProviderCalled[0] = true;
return super.extendCredentialProvider(httpClientBuilder, credentialsProvider, authData);
}
};
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritApi gerritClient = gerritRestApiFactory.create(new GerritAuthData.Basic(jettyUrl), httpClientBuilderExtension);
gerritClient.changes().query().get();
Truth.assertThat(extendCalled[0]).isTrue();
Truth.assertThat(extendCredentialProviderCalled[0]).isTrue();
}
@Test
public void testVersion() throws Exception {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritApi gerritClient = gerritRestApiFactory.create(new GerritAuthData.Basic(jettyUrl));
String version = gerritClient.config().server().getVersion();
Truth.assertThat(version).isEqualTo("2.10");
}
/**
* When cookie "GerritAccount" is available (sent in test with "LoginServlet"),
* "GerritAuth" string is extracted and cached.
* Note that no username / login is NOT sent - otherwise LoginSimulationServlet would
* not return a GerritAccount-cookie.
*/
@Test
public void testGerritAuthExtractionAndCache() throws Exception {
GerritRestClient gerritRestClient = new GerritRestClient(
new GerritAuthData.Basic(jettyUrl), new HttpRequestExecutor());
Field loginCacheField = gerritRestClient.getClass().getDeclaredField("loginCache");
loginCacheField.setAccessible(true);
LoginCache loginCache = (LoginCache) loginCacheField.get(gerritRestClient);
Truth.assertThat(loginCache.getGerritAuthOptional().isPresent()).isFalse();
gerritRestClient.requestRest("/changes/", null, GerritRestClient.HttpVerb.GET);
gerritRestClient.requestRest("/changes/?n=5", null, GerritRestClient.HttpVerb.GET);
Truth.assertThat(loginCache.getHostSupportsGerritAuth()).isTrue();
Truth.assertThat(loginCache.getGerritAuthOptional()).isPresent();
loginCache.invalidate();
Truth.assertThat(loginCache.getGerritAuthOptional().isPresent()).isFalse();
// ensure that even with invalidated cache request is possible and cached filled again
gerritRestClient.requestRest("/changes/", null, GerritRestClient.HttpVerb.GET);
Truth.assertThat(loginCache.getGerritAuthOptional()).isPresent();
}
/**
* Tests that the login cache is used correctly for a host which does NOT
* support Gerrit-Auth method. It tries the first time to get the GerritAccount-cookie
* and if that fails it continues to use HTTP auth.
*/
@Test
public void testGerritAuthNotAvailable() throws Exception {
GerritRestClient gerritRestClient = new GerritRestClient(
new GerritAuthData.Basic(jettyUrl, "foo", "bar"), new HttpRequestExecutor());
Field loginCacheField = gerritRestClient.getClass().getDeclaredField("loginCache");
loginCacheField.setAccessible(true);
LoginCache loginCache = (LoginCache) loginCacheField.get(gerritRestClient);
Truth.assertThat(loginCache.getGerritAuthOptional().isPresent()).isFalse();
Truth.assertThat(loginCache.getHostSupportsGerritAuth()).isTrue();
requestChanges(gerritRestClient);
Truth.assertThat(loginCache.getHostSupportsGerritAuth()).isFalse();
Truth.assertThat(loginCache.getGerritAuthOptional().isPresent()).isFalse();
requestChanges(gerritRestClient);
Truth.assertThat(loginCache.getGerritAuthOptional().isPresent()).isFalse();
}
private void requestChanges(GerritRestClient gerritRestClient) throws IOException, HttpStatusException {
try {
gerritRestClient.requestRest("/changes/", null, GerritRestClient.HttpVerb.GET);
} catch (HttpStatusException e) {
if (e.getStatusCode() != 404) { // 404 is expected since path /a/changes is not mapped
throw e;
}
}
}
@Test(enabled = false) // requires running Gerrit instance
public void testBasicRestCallToLocalhost() throws Exception {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritApi gerritClient = gerritRestApiFactory.create(new GerritAuthData.Basic("http://localhost:8080"));
List<ChangeInfo> changes = gerritClient.changes().query().get();
System.out.println(String.format("Got %s changes.", changes.size()));
System.out.println(changes);
}
@Test(enabled = false) // requires running Gerrit instance
public void testBasicRestCallToLocalhostProjects() throws Exception {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritApi gerritClient = gerritRestApiFactory.create(new GerritAuthData.Basic("http://localhost:8080"));
List<ProjectInfo> projects = gerritClient.projects().list().get();
System.out.println(String.format("Got %s projects.", projects.size()));
System.out.println(projects);
}
@Test(enabled = false) // requires running Gerrit instance
public void testBasicRestCallToLocalhostProjectsQuery() throws Exception {
GerritRestApiFactory gerritRestApiFactory = new GerritRestApiFactory();
GerritApi gerritClient = gerritRestApiFactory.create(new GerritAuthData.Basic("http://localhost:8080"));
List<ProjectInfo> projects = gerritClient.projects().list().withLimit(1).withDescription(true).get();
System.out.println(String.format("Got %s projects.", projects.size()));
System.out.println(projects);
}
}