Added CF callable context to better handle client errors
Also added junits to commons-cf that test the error handling.
This commit is contained in:
@@ -37,6 +37,13 @@
|
||||
<groupId>io.projectreactor.ipc</groupId>
|
||||
<artifactId>reactor-netty</artifactId>
|
||||
<version>${reactor-netty}</version>
|
||||
</dependency>
|
||||
<!-- Test harness -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.10.19</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,66 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.cloudfoundry.uaa.UaaException;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
|
||||
import reactor.ipc.netty.channel.AbortedException;
|
||||
|
||||
public class CFCallableContext {
|
||||
|
||||
public static final String UNAUTHORIZED_ERROR = "unauthorized";
|
||||
|
||||
private final CFParamsProviderMessages paramsProviderMessages;
|
||||
private Throwable lastConnectionError;
|
||||
|
||||
public CFCallableContext(CFParamsProviderMessages paramsProviderMessages) {
|
||||
this.paramsProviderMessages = paramsProviderMessages;
|
||||
}
|
||||
|
||||
public <T> T checkConnection(Callable<T> callable) throws Exception {
|
||||
this.lastConnectionError = null;
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw convertToCfVscodeError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Exception convertToCfVscodeError(Exception e) {
|
||||
Throwable deepestCause = ExceptionUtil.getDeepestCause(e);
|
||||
if (deepestCause instanceof UaaException) {
|
||||
String error = ((UaaException) deepestCause).getError();
|
||||
if (error != null && error.contains(UNAUTHORIZED_ERROR)) {
|
||||
this.lastConnectionError = deepestCause;
|
||||
return new UnauthorizedException(this.paramsProviderMessages.unauthorised());
|
||||
}
|
||||
} else if (deepestCause instanceof AbortedException) {
|
||||
// This one is odd. It is thrown when a wrong token is specified, but
|
||||
// instead of getting an expected UaaException, this AbortedException is thrown instead by reactor netty.
|
||||
this.lastConnectionError = deepestCause;
|
||||
return new UnauthorizedException(this.paramsProviderMessages.unauthorised());
|
||||
} else if (deepestCause instanceof UnknownHostException) {
|
||||
this.lastConnectionError = deepestCause;
|
||||
String message = ExceptionUtil.getMessage(deepestCause);
|
||||
return new ConnectionException(this.paramsProviderMessages.noNetworkConnection() + " " + message);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
public boolean hasConnectionError() {
|
||||
return this.lastConnectionError != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
|
||||
|
||||
/**
|
||||
* Messages that are specific to a particular parameter provider
|
||||
*
|
||||
*/
|
||||
public interface CFParamsProviderMessages {
|
||||
|
||||
|
||||
String noTargetsFound();
|
||||
|
||||
String unauthorised();
|
||||
|
||||
String noNetworkConnection();
|
||||
|
||||
}
|
||||
@@ -39,12 +39,13 @@ public class CFTarget {
|
||||
*/
|
||||
private final LoadingCache<String, List<CFBuildpack>> buildpacksCache;
|
||||
private final LoadingCache<String, List<CFServiceInstance>> servicesCache;
|
||||
private Throwable lastCFFailure;
|
||||
private CFCallableContext callableContext;
|
||||
|
||||
public CFTarget(String targetName, CFClientParams params, ClientRequests requests) {
|
||||
public CFTarget(String targetName, CFClientParams params, ClientRequests requests, CFCallableContext callableContext) {
|
||||
this.params = params;
|
||||
this.requests = requests;
|
||||
this.targetName = targetName;
|
||||
this.callableContext = callableContext;
|
||||
CacheLoader<String, List<CFServiceInstance>> servicesLoader = new CacheLoader<String, List<CFServiceInstance>>() {
|
||||
|
||||
@Override
|
||||
@@ -73,24 +74,11 @@ public class CFTarget {
|
||||
}
|
||||
|
||||
protected <T> T runAndCheckForFailure(Callable<T> callable) throws Exception {
|
||||
this.lastCFFailure = null;
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
if (isAcceptableCFFailure(e)) {
|
||||
this.lastCFFailure = e;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return callableContext.checkConnection(callable);
|
||||
}
|
||||
|
||||
public boolean hasCFFailure() {
|
||||
return this.lastCFFailure != null;
|
||||
}
|
||||
|
||||
protected boolean isAcceptableCFFailure(Throwable e) {
|
||||
// TODO: this is too broad. Be more specific: e.g. look for IOException, etc..
|
||||
return e != null;
|
||||
public boolean hasConnectionError() {
|
||||
return callableContext.hasConnectionError();
|
||||
}
|
||||
|
||||
public CFClientParams getParams() {
|
||||
|
||||
@@ -28,9 +28,11 @@ public class CFTargetCache {
|
||||
private final CloudFoundryClientFactory clientFactory;
|
||||
private final ClientTimeouts timeouts;
|
||||
private final LoadingCache<ClientParamsCacheKey, CFTarget> cache;
|
||||
private final CFCallableContext callableContext;
|
||||
|
||||
public static final long SERVICES_EXPIRATION = 10;
|
||||
public static final long TARGET_EXPIRATION = 1;
|
||||
|
||||
|
||||
public CFTargetCache(ClientParamsProvider paramsProvider, CloudFoundryClientFactory clientFactory,
|
||||
ClientTimeouts timeouts) {
|
||||
@@ -39,6 +41,7 @@ public class CFTargetCache {
|
||||
this.paramsProvider = paramsProvider;
|
||||
this.clientFactory = clientFactory;
|
||||
this.timeouts = timeouts;
|
||||
this.callableContext = new CFCallableContext(paramsProvider.getMessages());
|
||||
CacheLoader<ClientParamsCacheKey, CFTarget> loader = new CacheLoader<ClientParamsCacheKey, CFTarget>() {
|
||||
|
||||
@Override
|
||||
@@ -58,6 +61,10 @@ public class CFTargetCache {
|
||||
* for any other error encountered
|
||||
*/
|
||||
public synchronized List<CFTarget> getOrCreate() throws NoTargetsException, Exception {
|
||||
return callableContext.checkConnection(() -> doGetOrCreate());
|
||||
}
|
||||
|
||||
protected synchronized List<CFTarget> doGetOrCreate() throws NoTargetsException, Exception {
|
||||
|
||||
List<CFClientParams> allParams = paramsProvider.getParams();
|
||||
List<CFTarget> targets = new ArrayList<>();
|
||||
@@ -67,7 +74,7 @@ public class CFTargetCache {
|
||||
CFTarget target = cache.get(key);
|
||||
if (target != null) {
|
||||
// If any CF errors occurred in the target, refresh once
|
||||
if (target.hasCFFailure()) {
|
||||
if (target.hasConnectionError()) {
|
||||
cache.refresh(key);
|
||||
target = cache.get(key);
|
||||
}
|
||||
@@ -80,7 +87,7 @@ public class CFTargetCache {
|
||||
}
|
||||
|
||||
protected CFTarget create(CFClientParams params) throws Exception {
|
||||
return new CFTarget(getTargetName(params), params, clientFactory.getClient(params, timeouts));
|
||||
return new CFTarget(getTargetName(params), params, clientFactory.getClient(params, timeouts), callableContext);
|
||||
}
|
||||
|
||||
protected static String getTargetName(CFClientParams params) {
|
||||
|
||||
@@ -29,11 +29,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
*/
|
||||
public class CfCliParamsProvider implements ClientParamsProvider {
|
||||
|
||||
/*
|
||||
* Important: be sure to use ':' to separate the initial part of the message with a longer portion. The vscode
|
||||
* content assist will parse around the first ':' and the second segment will appear as a doc string that can be longer
|
||||
*/
|
||||
public static final String NO_CLI_TARGETS_FOUND_MESSAGE = "No Cloud Foundry targets found: Use CF CLI to login";
|
||||
|
||||
public static final String TARGET = "Target";
|
||||
public static final String REFRESH_TOKEN = "RefreshToken";
|
||||
@@ -41,6 +36,8 @@ public class CfCliParamsProvider implements ClientParamsProvider {
|
||||
public static final String SPACE_FIELDS = "SpaceFields";
|
||||
public static final String NAME = "Name";
|
||||
public static final String SSL_DISABLED = "SSLDisabled";
|
||||
private CfCliProviderMessages cfCliProviderMessages = new CfCliProviderMessages();
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -79,8 +76,7 @@ public class CfCliParamsProvider implements ClientParamsProvider {
|
||||
}
|
||||
|
||||
if (params.isEmpty()) {
|
||||
throw new NoTargetsException(
|
||||
NO_CLI_TARGETS_FOUND_MESSAGE);
|
||||
throw new NoTargetsException(getMessages().noTargetsFound());
|
||||
} else {
|
||||
return params;
|
||||
}
|
||||
@@ -109,4 +105,9 @@ public class CfCliParamsProvider implements ClientParamsProvider {
|
||||
return System.getProperty("user.home");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CFParamsProviderMessages getMessages() {
|
||||
return cfCliProviderMessages;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
|
||||
|
||||
public final class CfCliProviderMessages implements CFParamsProviderMessages {
|
||||
|
||||
/*
|
||||
* Important: be sure to use ':' to separate the initial part of the message
|
||||
* with a longer portion. The vscode content assist will parse around the
|
||||
* first ':' and the second segment will appear as a doc string that can be
|
||||
* longer
|
||||
*/
|
||||
public static final String NO_CLI_TARGETS_FOUND_MESSAGE = "No Cloud Foundry targets found: Use cf CLI to login";
|
||||
public static final String UNAUTHORISED_MESSAGE = "Unauthorized access: Use cf CLI to login";
|
||||
public static final String NO_NETWORK_CONNECTION = "Unable to connect to network: Verify network connection";
|
||||
|
||||
@Override
|
||||
public String noTargetsFound() {
|
||||
return NO_CLI_TARGETS_FOUND_MESSAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String unauthorised() {
|
||||
return UNAUTHORISED_MESSAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String noNetworkConnection() {
|
||||
return NO_NETWORK_CONNECTION;
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,6 @@ public interface ClientParamsProvider {
|
||||
* @throws ExecutionException if failure occurs while resolving params
|
||||
*/
|
||||
List<CFClientParams> getParams() throws NoTargetsException, ExecutionException;
|
||||
|
||||
CFParamsProviderMessages getMessages();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
|
||||
|
||||
public class ConnectionException extends Exception {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
|
||||
|
||||
public class UnauthorizedException extends Exception {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UnauthorizedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -10,39 +10,114 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.cloudfoundry.client;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.cloudfoundry.uaa.UaaException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFParamsProviderMessages;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliParamsProvider;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliProviderMessages;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ConnectionException;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.UnauthorizedException;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
|
||||
import reactor.ipc.netty.channel.AbortedException;
|
||||
|
||||
public class CFClientTest {
|
||||
|
||||
/*
|
||||
* Not meant to be run in a build yet as there is no CF harness to read CF
|
||||
* params. Just keeping this tests for local development.
|
||||
*/
|
||||
@Ignore
|
||||
MockCfCli cloudfoundry = new MockCfCli();
|
||||
ClientTimeouts timeouts = new ClientTimeouts();
|
||||
CFTargetCache targetCache;
|
||||
CFParamsProviderMessages expectedMessages = new CfCliProviderMessages();
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
targetCache = new CFTargetCache(cloudfoundry.paramsProvider, cloudfoundry.factory, timeouts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCliParamsTarget() throws Exception {
|
||||
public void testNoTarget() throws Exception {
|
||||
|
||||
ClientParamsProvider cliProvider = new CfCliParamsProvider();
|
||||
CloudFoundryClientFactory clientFactory = DefaultCloudFoundryClientFactoryV2.INSTANCE;
|
||||
ClientTimeouts timeouts = ClientTimeouts.DEFAULT_TIMEOUTS;
|
||||
when(cloudfoundry.paramsProvider.getParams())
|
||||
.thenThrow(new NoTargetsException(expectedMessages.noTargetsFound()));
|
||||
assertError(() -> targetCache.getOrCreate(), NoTargetsException.class, expectedMessages.noTargetsFound());
|
||||
}
|
||||
|
||||
CFTargetCache targetCache = new CFTargetCache(cliProvider, clientFactory, timeouts);
|
||||
@Test
|
||||
public void testOneTarget() throws Exception {
|
||||
CFTarget target = targetCache.getOrCreate().get(0);
|
||||
assertNotNull(target);
|
||||
assertEquals(MockCfCli.DEFAULT_PARAMS, target.getParams());
|
||||
}
|
||||
|
||||
List<CFBuildpack> buildPacks = target.getBuildpacks();
|
||||
assertTrue(!buildPacks.isEmpty());
|
||||
List<CFServiceInstance> services = target.getServices();
|
||||
assertTrue(!services.isEmpty());
|
||||
@Test
|
||||
public void testUnknownHostServices() throws Exception {
|
||||
ClientRequests client = cloudfoundry.client;
|
||||
when(client.getServices()).thenThrow(new UnknownHostException("api.run.pivotal.io"));
|
||||
CFTarget target = targetCache.getOrCreate().get(0);
|
||||
assertError(() -> target.getServices(), ConnectionException.class, expectedMessages.noNetworkConnection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownHostBuildpacks() throws Exception {
|
||||
ClientRequests client = cloudfoundry.client;
|
||||
when(client.getBuildpacks()).thenThrow(new UnknownHostException("api.run.pivotal.io"));
|
||||
CFTarget target = targetCache.getOrCreate().get(0);
|
||||
assertError(() -> target.getBuildpacks(), ConnectionException.class, expectedMessages.noNetworkConnection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbortedExceptionServices() throws Exception {
|
||||
ClientRequests client = cloudfoundry.client;
|
||||
when(client.getServices()).thenThrow(new AbortedException("connection aborted"));
|
||||
CFTarget target = targetCache.getOrCreate().get(0);
|
||||
assertError(() -> target.getServices(), UnauthorizedException.class, expectedMessages.unauthorised());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbortedExceptionBuildpacks() throws Exception {
|
||||
ClientRequests client = cloudfoundry.client;
|
||||
when(client.getBuildpacks()).thenThrow(new AbortedException("connection aborted"));
|
||||
CFTarget target = targetCache.getOrCreate().get(0);
|
||||
assertError(() -> target.getBuildpacks(), UnauthorizedException.class, expectedMessages.unauthorised());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUaaExceptionServices() throws Exception {
|
||||
ClientRequests client = cloudfoundry.client;
|
||||
when(client.getServices()).thenThrow(new UaaException(401, "unauthorized", "Bad credentials"));
|
||||
CFTarget target = targetCache.getOrCreate().get(0);
|
||||
assertError(() -> target.getServices(), UnauthorizedException.class, expectedMessages.unauthorised());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUaaExceptionBuildpacks() throws Exception {
|
||||
ClientRequests client = cloudfoundry.client;
|
||||
when(client.getBuildpacks()).thenThrow(new UaaException(401, "unauthorized", "Bad credentials"));
|
||||
CFTarget target = targetCache.getOrCreate().get(0);
|
||||
assertError(() -> target.getBuildpacks(), UnauthorizedException.class, expectedMessages.unauthorised());
|
||||
}
|
||||
|
||||
protected void assertError(Callable<?> callable, Class<? extends Throwable> expected, String expectedMessage)
|
||||
throws Exception {
|
||||
Throwable error = null;
|
||||
|
||||
try {
|
||||
callable.call();
|
||||
} catch (Exception e) {
|
||||
error = ExceptionUtil.getDeepestCause(e);
|
||||
}
|
||||
assertEquals(expected, error.getClass());
|
||||
assertTrue(ExceptionUtil.getMessageNoAppendedInformation(error).contains(expectedMessage));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.cloudfoundry.client;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliProviderMessages;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class MockCfCli {
|
||||
|
||||
public final static CFClientParams DEFAULT_PARAMS = new CFClientParams("test.io", "testuser",
|
||||
CFCredentials.fromRefreshToken("refreshtoken"), false);
|
||||
|
||||
public final CloudFoundryClientFactory factory = mock(CloudFoundryClientFactory.class);
|
||||
public final ClientRequests client = mock(ClientRequests.class);
|
||||
public final ClientParamsProvider paramsProvider = mock(ClientParamsProvider.class);
|
||||
public final CfCliProviderMessages actualCfCliMessages = new CfCliProviderMessages();
|
||||
|
||||
public MockCfCli() {
|
||||
try {
|
||||
//program some default behavior into mocks... most tests will use this.
|
||||
//other tests should 'reset' the mocks and reprogram them as needed.
|
||||
when(factory.getClient(any(), any())).thenReturn(client);
|
||||
when(paramsProvider.getParams()).thenReturn(ImmutableList.of(DEFAULT_PARAMS));
|
||||
when(paramsProvider.getMessages()).thenReturn(actualCfCliMessages);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtil.unchecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the mocks. Use this if the default's programmed into the mocks don't suite your test case.
|
||||
* <p>
|
||||
* Note: you may also choose to call {@link Mockito}.mock directly if you do not want to
|
||||
* reset all of the mocks.
|
||||
*/
|
||||
public void reset() throws Exception {
|
||||
Mockito.reset(factory, client, paramsProvider);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@@ -56,6 +57,22 @@ public class ExceptionUtil {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an exception, find if any of the exception types to look for is contained in the given exception
|
||||
* @param e
|
||||
* @param toLookFor non-null list of exception types to look for
|
||||
* @return exception of specified type, if found, or null if not found
|
||||
*/
|
||||
public static Throwable findThrowable(Throwable e, List<Class<? extends Throwable>> toLookFor) {
|
||||
for (Class<? extends Throwable> klass : toLookFor) {
|
||||
Throwable found = getThrowable(e, klass);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getMessage(Throwable e) {
|
||||
// The message of nested exception is usually more interesting than the
|
||||
|
||||
@@ -19,11 +19,15 @@ import java.util.logging.Logger;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.UnauthorizedException;
|
||||
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ConnectionException;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParseException;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public abstract class AbstractCFHintsProvider implements Callable<Collection<YValueHint>> {
|
||||
|
||||
public static final String EMPTY_VALUE = "";
|
||||
@@ -59,12 +63,12 @@ public abstract class AbstractCFHintsProvider implements Callable<Collection<YVa
|
||||
// resolving the error message. Instead, log the full error, and
|
||||
// only throw a
|
||||
// new exception with a "nicer" message
|
||||
Throwable noTargetsError = ExceptionUtil.getThrowable(e, NoTargetsException.class);
|
||||
if (noTargetsError != null) {
|
||||
Throwable errorNoAppending = getErrorNoAppending(e);
|
||||
if (errorNoAppending != null) {
|
||||
// Do not log the no-targets exception as it may be encountered
|
||||
// frequently
|
||||
// if a user does not have a CF client installed
|
||||
throw new ValueParseException(ExceptionUtil.getMessageNoAppendedInformation(noTargetsError));
|
||||
throw new ValueParseException(ExceptionUtil.getMessageNoAppendedInformation(errorNoAppending));
|
||||
} else {
|
||||
// Log any other error
|
||||
logger.log(Level.SEVERE, ExceptionUtil.getMessage(e), e);
|
||||
@@ -74,6 +78,17 @@ public abstract class AbstractCFHintsProvider implements Callable<Collection<YVa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
* @return an error that requires no additional information when showing its
|
||||
* message, or null if no such error is found
|
||||
*/
|
||||
protected Throwable getErrorNoAppending(Throwable e) {
|
||||
return ExceptionUtil.findThrowable(e,
|
||||
ImmutableList.of(NoTargetsException.class, ConnectionException.class, UnauthorizedException.class));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return non-null list of hints. Return empty if no hints available
|
||||
|
||||
Reference in New Issue
Block a user