Merge branch 'develop' into bugfix/api-key-to-access-token

This commit is contained in:
Konstantin Schaper
2020-11-05 10:14:52 +01:00
28 changed files with 1109 additions and 213 deletions

View File

@@ -24,6 +24,7 @@
package sonia.scm.admin;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,6 +47,9 @@ public class ReleaseFeedParser {
public static final int DEFAULT_TIMEOUT_IN_MILLIS = 1000;
private static final Logger LOG = LoggerFactory.getLogger(ReleaseFeedParser.class);
@VisibleForTesting
static final String SPAN_KIND = "Release Feed";
private final AdvancedHttpClient client;
private final ExecutorService executorService;
@@ -103,7 +107,10 @@ public class ReleaseFeedParser {
if (Strings.isNullOrEmpty(url)) {
return Optional.empty();
}
ReleaseFeedDto releaseFeed = client.get(url).request().contentFromXml(ReleaseFeedDto.class);
ReleaseFeedDto releaseFeed = client.get(url)
.spanKind(SPAN_KIND)
.request()
.contentFromXml(ReleaseFeedDto.class);
return filterForLatestRelease(releaseFeed);
} catch (Exception e) {
LOG.error("Could not parse release feed from {}", url, e);

View File

@@ -30,6 +30,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.plugin.Extension;
import sonia.scm.security.AnonymousMode;
import sonia.scm.security.PermissionAssigner;
@@ -44,6 +46,8 @@ import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.Collections;
import static sonia.scm.group.GroupCollector.AUTHENTICATED;
@Extension
public class SetupContextListener implements ServletContextListener {
@@ -75,13 +79,18 @@ public class SetupContextListener implements ServletContextListener {
private final PasswordService passwordService;
private final PermissionAssigner permissionAssigner;
private final ScmConfiguration scmConfiguration;
private final GroupManager groupManager;
@VisibleForTesting
static final String AUTHENTICATED_GROUP_DESCRIPTION = "Includes all authenticated users";
@Inject
public SetupAction(UserManager userManager, PasswordService passwordService, PermissionAssigner permissionAssigner, ScmConfiguration scmConfiguration) {
public SetupAction(UserManager userManager, PasswordService passwordService, PermissionAssigner permissionAssigner, ScmConfiguration scmConfiguration, GroupManager groupManager) {
this.userManager = userManager;
this.passwordService = passwordService;
this.permissionAssigner = permissionAssigner;
this.scmConfiguration = scmConfiguration;
this.groupManager = groupManager;
}
@Override
@@ -92,6 +101,10 @@ public class SetupContextListener implements ServletContextListener {
if (anonymousUserRequiredButNotExists()) {
userManager.create(SCMContext.ANONYMOUS);
}
if (authenticatedGroupDoesNotExists()) {
createAuthenticatedGroup();
}
}
private boolean anonymousUserRequiredButNotExists() {
@@ -115,5 +128,16 @@ public class SetupContextListener implements ServletContextListener {
PermissionDescriptor descriptor = new PermissionDescriptor("*");
permissionAssigner.setPermissionsForUser("scmadmin", Collections.singleton(descriptor));
}
private boolean authenticatedGroupDoesNotExists() {
return groupManager.get(AUTHENTICATED) == null;
}
private void createAuthenticatedGroup() {
Group authenticated = new Group("xml", AUTHENTICATED);
authenticated.setDescription(AUTHENTICATED_GROUP_DESCRIPTION);
authenticated.setExternal(true);
groupManager.create(authenticated);
}
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.net.ahc;
//~--- non-JDK imports --------------------------------------------------------
@@ -38,8 +38,11 @@ import sonia.scm.config.ScmConfiguration;
import sonia.scm.net.Proxies;
import sonia.scm.net.TrustAllHostnameVerifier;
import sonia.scm.net.TrustAllTrustManager;
import sonia.scm.trace.Span;
import sonia.scm.trace.Tracer;
import sonia.scm.util.HttpUtil;
import javax.annotation.Nonnull;
import javax.inject.Provider;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@@ -99,9 +102,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
*/
@Inject
public DefaultAdvancedHttpClient(ScmConfiguration configuration,
Set<ContentTransformer> contentTransformers, Provider<SSLContext> sslContextProvider)
Tracer tracer, Set<ContentTransformer> contentTransformers, Provider<SSLContext> sslContextProvider)
{
this.configuration = configuration;
this.tracer = tracer;
this.contentTransformers = contentTransformers;
this.sslContextProvider = sslContextProvider;
}
@@ -185,45 +189,65 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
* @throws IOException
*/
@Override
protected AdvancedHttpResponse request(BaseHttpRequest<?> request)
throws IOException
{
HttpURLConnection connection = openConnection(request,
new URL(request.getUrl()));
protected AdvancedHttpResponse request(BaseHttpRequest<?> request) throws IOException {
String spanKind = request.getSpanKind();
if (Strings.isNullOrEmpty(spanKind)) {
logger.debug("execute request {} without tracing", request.getUrl());
return doRequest(request);
}
return doRequestWithTracing(request);
}
@Nonnull
private DefaultAdvancedHttpResponse doRequestWithTracing(BaseHttpRequest<?> request) throws IOException {
try (Span span = tracer.span(request.getSpanKind())) {
span.label("url", request.getUrl());
span.label("method", request.getMethod());
try {
DefaultAdvancedHttpResponse response = doRequest(request);
span.label("status", response.getStatus());
if (!response.isSuccessful()) {
span.failed();
}
return response;
} catch (IOException ex) {
span.label("exception", ex.getClass().getName());
span.label("message", ex.getMessage());
span.failed();
throw ex;
}
}
}
@Nonnull
private DefaultAdvancedHttpResponse doRequest(BaseHttpRequest<?> request) throws IOException {
HttpURLConnection connection = openConnection(request, new URL(request.getUrl()));
applyBaseSettings(request, connection);
if (connection instanceof HttpsURLConnection)
{
if (connection instanceof HttpsURLConnection) {
applySSLSettings(request, (HttpsURLConnection) connection);
}
Content content = null;
if (request instanceof AdvancedHttpRequestWithBody)
{
if (request instanceof AdvancedHttpRequestWithBody) {
AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request;
content = ahrwb.getContent();
if (content != null)
{
if (content != null) {
content.prepare(ahrwb);
}
else
{
} else {
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
}
}
else
{
} else {
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
}
applyHeaders(request, connection);
if (content != null)
{
if (content != null) {
applyContent(connection, content);
}
@@ -300,7 +324,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
{
TrustManager[] trustAllCerts = new TrustManager[] {
new TrustAllTrustManager() };
SSLContext sc = SSLContext.getInstance("SSL");
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
connection.setSSLSocketFactory(sc.getSocketFactory());
@@ -309,10 +333,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
{
logger.error("could not disable certificate validation", ex);
}
}
else
}
else
{
logger.trace("set ssl socker factory from provider");
logger.trace("set ssl socket factory from provider");
connection.setSSLSocketFactory(sslContextProvider.get().getSocketFactory());
}
@@ -330,7 +354,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
if (isProxyEnabled(request))
{
connection = openProxyConnection(request, url);
connection = openProxyConnection(url);
appendProxyAuthentication(connection);
}
else
@@ -340,7 +364,9 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
logger.trace("ignore proxy settings");
}
logger.debug("fetch {}", url.toExternalForm());
if (logger.isDebugEnabled()) {
logger.debug("fetch {}", url.toExternalForm());
}
connection = createConnection(url);
}
@@ -348,8 +374,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
return connection;
}
private HttpURLConnection openProxyConnection(BaseHttpRequest<?> request,
URL url)
private HttpURLConnection openProxyConnection(URL url)
throws IOException
{
if (logger.isDebugEnabled())
@@ -380,7 +405,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
/** set of content transformers */
private final Set<ContentTransformer> contentTransformers;
/** ssl context provider */
private final Provider<SSLContext> sslContextProvider;
/** tracer used for request tracing */
private final Tracer tracer;
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.plugin;
import com.google.common.annotations.VisibleForTesting;
@@ -34,6 +34,8 @@ import javax.inject.Inject;
import java.util.Collections;
import java.util.Set;
import static sonia.scm.plugin.Tracing.SPAN_KIND;
class PluginCenterLoader {
private static final Logger LOG = LoggerFactory.getLogger(PluginCenterLoader.class);
@@ -57,7 +59,8 @@ class PluginCenterLoader {
Set<AvailablePlugin> load(String url) {
try {
LOG.info("fetch plugins from {}", url);
PluginCenterDto pluginCenterDto = client.get(url).request().contentFromJson(PluginCenterDto.class);
PluginCenterDto pluginCenterDto = client.get(url).spanKind(SPAN_KIND).request()
.contentFromJson(PluginCenterDto.class);
return mapper.map(pluginCenterDto);
} catch (Exception ex) {
LOG.error("failed to load plugins from plugin center, returning empty list", ex);

View File

@@ -38,6 +38,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import static sonia.scm.plugin.Tracing.SPAN_KIND;
@SuppressWarnings("UnstableApiUsage")
// guava hash is marked as unstable
class PluginInstaller {
@@ -126,7 +128,7 @@ class PluginInstaller {
}
private InputStream download(AvailablePlugin plugin) throws IOException {
return client.get(plugin.getDescriptor().getUrl()).request().contentAsStream();
return client.get(plugin.getDescriptor().getUrl()).spanKind(SPAN_KIND).request().contentAsStream();
}
private Path createFile(AvailablePlugin plugin) throws IOException {

View File

@@ -0,0 +1,33 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.plugin;
final class Tracing {
public static final String SPAN_KIND = "Plugin Center";
private Tracing() {
}
}

View File

@@ -0,0 +1,78 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.trace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.Extension;
import javax.inject.Inject;
import java.util.Map;
import java.util.function.Consumer;
/**
* An {@link Exporter} which logs every collected span.
*
* @since 2.9.0
*/
@Extension
public final class LoggingExporter implements Exporter {
private static final Logger LOG = LoggerFactory.getLogger(LoggingExporter.class);
private final Consumer<String> logger;
@Inject
LoggingExporter() {
this(LOG::info);
}
LoggingExporter(Consumer<String> logger) {
this.logger = logger;
}
@Override
public void export(SpanContext span) {
logger.accept(format(span));
}
private String format(SpanContext span) {
StringBuilder message = new StringBuilder("received ");
if (span.isFailed()) {
message.append("failed ");
}
message.append(span.getKind()).append(" span, which took ");
message.append(span.duration().toMillis()).append("ms");
Map<String, String> labels = span.getLabels();
if (!labels.isEmpty()) {
message.append(":");
for (Map.Entry<String, String> e : labels.entrySet()) {
message.append("\n - ").append(e.getKey()).append(": ").append(e.getValue());
}
}
return message.toString();
}
}

View File

@@ -32,6 +32,7 @@ import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.net.ahc.AdvancedHttpResponse;
import java.io.IOException;
import java.util.Date;
@@ -44,6 +45,7 @@ import java.util.concurrent.Semaphore;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static sonia.scm.admin.ReleaseFeedParser.SPAN_KIND;
@ExtendWith(MockitoExtension.class)
class ReleaseFeedParserTest {
@@ -62,7 +64,7 @@ class ReleaseFeedParserTest {
void shouldFindLatestRelease() throws IOException {
String url = "https://www.scm-manager.org/download/rss.xml";
when(client.get(url).request().contentFromXml(ReleaseFeedDto.class)).thenReturn(createReleaseFeedDto());
when(request(url).contentFromXml(ReleaseFeedDto.class)).thenReturn(createReleaseFeedDto());
Optional<UpdateInfo> update = releaseFeedParser.findLatestRelease(url);
@@ -71,13 +73,17 @@ class ReleaseFeedParserTest {
assertThat(update.get().getLink()).isEqualTo("download-3");
}
private AdvancedHttpResponse request(String url) throws IOException {
return client.get(url).spanKind(SPAN_KIND).request();
}
@Test
void shouldHandleTimeout() throws IOException {
String url = "https://www.scm-manager.org/download/rss.xml";
Semaphore waitWithResultUntilTimeout = new Semaphore(0);
when(client.get(url).request().contentFromXml(ReleaseFeedDto.class)).thenAnswer(invocation -> {
when(request(url).contentFromXml(ReleaseFeedDto.class)).thenAnswer(invocation -> {
waitWithResultUntilTimeout.acquire();
return createReleaseFeedDto();
});
@@ -95,7 +101,7 @@ class ReleaseFeedParserTest {
Semaphore waitWithResultUntilBothTriggered = new Semaphore(0);
when(client.get(url).request().contentFromXml(ReleaseFeedDto.class)).thenAnswer(invocation -> {
when(request(url).contentFromXml(ReleaseFeedDto.class)).thenAnswer(invocation -> {
waitWithResultUntilBothTriggered.acquire();
return createReleaseFeedDto();
});

View File

@@ -37,6 +37,8 @@ import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.security.AnonymousMode;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
@@ -56,6 +58,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static sonia.scm.group.GroupCollector.AUTHENTICATED;
import static sonia.scm.lifecycle.SetupContextListener.SetupAction.AUTHENTICATED_GROUP_DESCRIPTION;
@ExtendWith(MockitoExtension.class)
class SetupContextListenerTest {
@@ -75,6 +79,9 @@ class SetupContextListenerTest {
@Mock
ScmConfiguration scmConfiguration;
@Mock
private GroupManager groupManager;
@Mock
private PermissionAssigner permissionAssigner;
@@ -96,6 +103,7 @@ class SetupContextListenerTest {
@Test
void shouldCreateAdminAccountIfNoUserExistsAndAssignPermissions() {
when(groupManager.get(AUTHENTICATED)).thenReturn(createAuthenticatedGroup());
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
setupContextListener.contextInitialized(null);
@@ -108,6 +116,7 @@ class SetupContextListenerTest {
void shouldCreateAdminAccountIfOnlyAnonymousUserExistsAndAssignPermissions() {
when(userManager.getAll()).thenReturn(Lists.newArrayList(SCMContext.ANONYMOUS));
when(userManager.contains(SCMContext.USER_ANONYMOUS)).thenReturn(true);
when(groupManager.get(AUTHENTICATED)).thenReturn(createAuthenticatedGroup());
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
setupContextListener.contextInitialized(null);
@@ -135,6 +144,7 @@ class SetupContextListenerTest {
void shouldDoNothingOnSecondStart() {
List<User> users = Lists.newArrayList(UserTestData.createTrillian());
when(userManager.getAll()).thenReturn(users);
when(groupManager.get(AUTHENTICATED)).thenReturn(createAuthenticatedGroup());
setupContextListener.contextInitialized(null);
@@ -146,6 +156,7 @@ class SetupContextListenerTest {
void shouldCreateAnonymousUserIfRequired() {
List<User> users = Lists.newArrayList(UserTestData.createTrillian());
when(userManager.getAll()).thenReturn(users);
when(groupManager.get(AUTHENTICATED)).thenReturn(createAuthenticatedGroup());
when(scmConfiguration.getAnonymousMode()).thenReturn(AnonymousMode.FULL);
setupContextListener.contextInitialized(null);
@@ -157,6 +168,7 @@ class SetupContextListenerTest {
void shouldNotCreateAnonymousUserIfNotRequired() {
List<User> users = Lists.newArrayList(UserTestData.createTrillian());
when(userManager.getAll()).thenReturn(users);
when(groupManager.get(AUTHENTICATED)).thenReturn(createAuthenticatedGroup());
setupContextListener.contextInitialized(null);
@@ -167,6 +179,7 @@ class SetupContextListenerTest {
void shouldNotCreateAnonymousUserIfAlreadyExists() {
List<User> users = Lists.newArrayList(SCMContext.ANONYMOUS);
when(userManager.getAll()).thenReturn(users);
when(groupManager.get(AUTHENTICATED)).thenReturn(createAuthenticatedGroup());
when(scmConfiguration.getAnonymousMode()).thenReturn(AnonymousMode.FULL);
setupContextListener.contextInitialized(null);
@@ -174,6 +187,28 @@ class SetupContextListenerTest {
verify(userManager, times(1)).create(SCMContext.ANONYMOUS);
}
@Test
void shouldCreateAuthenticatedGroupIfMissing() {
when(groupManager.get(AUTHENTICATED)).thenReturn(null);
setupContextListener.contextInitialized(null);
Group authenticated = createAuthenticatedGroup();
authenticated.setDescription(AUTHENTICATED_GROUP_DESCRIPTION);
authenticated.setExternal(true);
verify(groupManager, times(1)).create(authenticated);
}
@Test
void shouldNotCreateAuthenticatedGroupIfAlreadyExists() {
when(groupManager.get(AUTHENTICATED)).thenReturn(createAuthenticatedGroup());
setupContextListener.contextInitialized(null);
verify(groupManager, never()).create(any());
}
private void verifyAdminPermissionsAssigned() {
ArgumentCaptor<String> usernameCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Collection<PermissionDescriptor>> permissionCaptor = ArgumentCaptor.forClass(Collection.class);
@@ -192,4 +227,7 @@ class SetupContextListenerTest {
assertThat(user.getPassword()).isEqualTo("secret");
}
private Group createAuthenticatedGroup() {
return new Group("xml", AUTHENTICATED);
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.net.ahc;
//~--- non-JDK imports --------------------------------------------------------
@@ -29,33 +29,29 @@ package sonia.scm.net.ahc;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.net.TrustAllHostnameVerifier;
import sonia.scm.trace.Span;
import sonia.scm.trace.Tracer;
import sonia.scm.util.HttpUtil;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketAddress;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import sonia.scm.net.SSLContextProvider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketAddress;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -82,12 +78,12 @@ public class DefaultAdvancedHttpClientTest
DefaultAdvancedHttpClient.TIMEOUT_CONNECTION);
verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0");
}
@Test(expected = ContentTransformerNotFoundException.class)
public void testContentTransformerNotFound(){
client.createTransformer(String.class, "text/plain");
}
@Test
public void testContentTransformer(){
ContentTransformer transformer = mock(ContentTransformer.class);
@@ -265,6 +261,63 @@ public class DefaultAdvancedHttpClientTest
"Basic dHJpY2lhOnRyaWNpYXMgc2VjcmV0");
}
@Test
public void shouldCreateTracingSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(200);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").spanKind("spaceships").request();
verify(tracer).span("spaceships");
verify(span).label("url", "https://www.scm-manager.org");
verify(span).label("method", "GET");
verify(span).label("status", 200);
verify(span, never()).failed();
verify(span).close();
}
@Test
public void shouldCreateFailedTracingSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(500);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").request();
verify(tracer).span("HTTP Request");
verify(span).label("url", "https://www.scm-manager.org");
verify(span).label("method", "GET");
verify(span).label("status", 500);
verify(span).failed();
verify(span).close();
}
@Test
public void shouldCreateFailedTracingSpanOnIOException() throws IOException {
when(connection.getResponseCode()).thenThrow(new IOException("failed"));
boolean thrown = false;
try {
new AdvancedHttpRequest(client, HttpMethod.DELETE, "http://failing.host").spanKind("failures").request();
} catch (IOException ex) {
thrown = true;
}
assertTrue(thrown);
verify(tracer).span("failures");
verify(span).label("url", "http://failing.host");
verify(span).label("method", "DELETE");
verify(span).label("exception", IOException.class.getName());
verify(span).label("message", "failed");
verify(span).failed();
verify(span).close();
}
@Test
public void shouldNotCreateSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(200);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org")
.disableTracing().request();
verify(tracer, never()).span(anyString());
}
//~--- set methods ----------------------------------------------------------
/**
@@ -277,6 +330,7 @@ public class DefaultAdvancedHttpClientTest
configuration = new ScmConfiguration();
transformers = new HashSet<ContentTransformer>();
client = new TestingAdvacedHttpClient(configuration, transformers);
when(tracer.span(anyString())).thenReturn(span);
}
//~--- inner classes --------------------------------------------------------
@@ -298,10 +352,9 @@ public class DefaultAdvancedHttpClientTest
* @param configuration
* @param transformers
*/
public TestingAdvacedHttpClient(ScmConfiguration configuration,
Set<ContentTransformer> transformers)
public TestingAdvacedHttpClient(ScmConfiguration configuration, Set<ContentTransformer> transformers)
{
super(configuration, transformers, new SSLContextProvider());
super(configuration, tracer, transformers, new SSLContextProvider());
}
//~--- methods ------------------------------------------------------------
@@ -364,4 +417,10 @@ public class DefaultAdvancedHttpClientTest
/** Field description */
private Set<ContentTransformer> transformers;
@Mock
private Tracer tracer;
@Mock
private Span span;
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.net.ahc;
//~--- non-JDK imports --------------------------------------------------------
@@ -33,9 +33,11 @@ import com.google.common.collect.Multimap;
import com.google.common.io.ByteSource;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -56,6 +58,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.trace.Tracer;
/**
*
@@ -65,6 +68,13 @@ import sonia.scm.net.SSLContextProvider;
public class DefaultAdvancedHttpResponseTest
{
private DefaultAdvancedHttpClient client;
@Before
public void setUpClient() {
client = new DefaultAdvancedHttpClient(new ScmConfiguration(), tracer, new HashSet<>(), new SSLContextProvider());
}
/**
* Method description
*
@@ -130,13 +140,10 @@ public class DefaultAdvancedHttpResponseTest
assertTrue(headers.get("Test-2").isEmpty());
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final DefaultAdvancedHttpClient client =
new DefaultAdvancedHttpClient(new ScmConfiguration(), new HashSet<>(), new SSLContextProvider());
/** Field description */
@Mock
private HttpURLConnection connection;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Tracer tracer;
}

View File

@@ -32,6 +32,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.event.ScmEventBus;
import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.net.ahc.AdvancedHttpResponse;
import java.io.IOException;
import java.util.Collections;
@@ -41,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static sonia.scm.plugin.Tracing.SPAN_KIND;
@ExtendWith(MockitoExtension.class)
class PluginCenterLoaderTest {
@@ -63,16 +65,20 @@ class PluginCenterLoaderTest {
void shouldFetch() throws IOException {
Set<AvailablePlugin> plugins = Collections.emptySet();
PluginCenterDto dto = new PluginCenterDto();
when(client.get(PLUGIN_URL).request().contentFromJson(PluginCenterDto.class)).thenReturn(dto);
when(request().contentFromJson(PluginCenterDto.class)).thenReturn(dto);
when(mapper.map(dto)).thenReturn(plugins);
Set<AvailablePlugin> fetched = loader.load(PLUGIN_URL);
assertThat(fetched).isSameAs(plugins);
}
private AdvancedHttpResponse request() throws IOException {
return client.get(PLUGIN_URL).spanKind(SPAN_KIND).request();
}
@Test
void shouldReturnEmptySetIfPluginCenterNotBeReached() throws IOException {
when(client.get(PLUGIN_URL).request()).thenThrow(new IOException("failed to fetch"));
when(request()).thenThrow(new IOException("failed to fetch"));
Set<AvailablePlugin> fetch = loader.load(PLUGIN_URL);
assertThat(fetch).isEmpty();
@@ -80,7 +86,7 @@ class PluginCenterLoaderTest {
@Test
void shouldFirePluginCenterErrorEvent() throws IOException {
when(client.get(PLUGIN_URL).request()).thenThrow(new IOException("failed to fetch"));
when(request()).thenThrow(new IOException("failed to fetch"));
loader.load(PLUGIN_URL);

View File

@@ -34,6 +34,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.net.ahc.AdvancedHttpResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -50,6 +51,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static sonia.scm.plugin.Tracing.SPAN_KIND;
@ExtendWith({MockitoExtension.class})
class PluginInstallerTest {
@@ -101,10 +103,14 @@ class PluginInstallerTest {
}
private void mockContent(String content) throws IOException {
when(client.get("https://download.hitchhiker.com").request().contentAsStream())
when(request("https://download.hitchhiker.com").contentAsStream())
.thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
}
private AdvancedHttpResponse request(String url) throws IOException {
return client.get(url).spanKind(SPAN_KIND).request();
}
private AvailablePlugin createGitPlugin() {
return createPlugin(
"scm-git-plugin",
@@ -115,7 +121,7 @@ class PluginInstallerTest {
@Test
void shouldThrowPluginDownloadException() throws IOException {
when(client.get("https://download.hitchhiker.com").request()).thenThrow(new IOException("failed to download"));
when(request("https://download.hitchhiker.com")).thenThrow(new IOException("failed to download"));
PluginInstallationContext context = PluginInstallationContext.empty();
AvailablePlugin gitPlugin = createGitPlugin();
@@ -136,7 +142,7 @@ class PluginInstallerTest {
void shouldThrowPluginDownloadExceptionAndCleanup() throws IOException {
InputStream stream = mock(InputStream.class);
when(stream.read(any(), anyInt(), anyInt())).thenThrow(new IOException("failed to read"));
when(client.get("https://download.hitchhiker.com").request().contentAsStream()).thenReturn(stream);
when(request("https://download.hitchhiker.com").contentAsStream()).thenReturn(stream);
PluginInstallationContext context = PluginInstallationContext.empty();
AvailablePlugin gitPlugin = createGitPlugin();

View File

@@ -0,0 +1,88 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.trace;
import com.google.common.collect.ImmutableMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
class LoggingExporterTest {
private String message;
private LoggingExporter exporter;
@BeforeEach
void setUpLogger() {
exporter = new LoggingExporter((message) -> this.message = message);
}
@Test
void shouldLogTheSpanKind() {
exporter.export(new SpanContext(
"AwesomeSpanKind", Collections.emptyMap(), Instant.now(), Instant.now(), false
));
assertThat(message).contains("AwesomeSpanKind");
}
@Test
void shouldLogFailed() {
exporter.export(new SpanContext(
"sample", Collections.emptyMap(), Instant.now(), Instant.now(), true
));
assertThat(message).contains("failed");
}
@Test
void shouldLogDuration() {
Instant opened = Instant.now();
exporter.export(new SpanContext(
"sample", ImmutableMap.of(), opened, opened.plusMillis(42L), false
));
assertThat(message).contains("42ms");
}
@Test
void shouldLogLabels() {
exporter.export(new SpanContext(
"sample", ImmutableMap.of("l1", "v1", "l2", "v2"), Instant.now(), Instant.now(), false
));
assertThat(message)
.contains("l1")
.contains("v1")
.contains("l2")
.contains("v2");
}
}