From de5226ff61b807e31bb08d849aff33e205fb6526 Mon Sep 17 00:00:00 2001 From: nsingh Date: Tue, 31 Jan 2017 15:52:04 -0800 Subject: [PATCH] Added CF callable context to better handle client errors Also added junits to commons-cf that test the error handling. --- vscode-extensions/commons/commons-cf/pom.xml | 7 ++ .../client/cftarget/CFCallableContext.java | 66 ++++++++++ .../cftarget/CFParamsProviderMessages.java | 26 ++++ .../client/cftarget/CFTarget.java | 24 +--- .../client/cftarget/CFTargetCache.java | 11 +- .../client/cftarget/CfCliParamsProvider.java | 15 +-- .../cftarget/CfCliProviderMessages.java | 39 ++++++ .../client/cftarget/ClientParamsProvider.java | 2 + .../client/cftarget/ConnectionException.java | 24 ++++ .../cftarget/UnauthorizedException.java | 23 ++++ .../cloudfoundry/client/CFClientTest.java | 113 +++++++++++++++--- .../cloudfoundry/client/MockCfCli.java | 61 ++++++++++ .../vscode/commons/util/ExceptionUtil.java | 17 +++ .../yaml/AbstractCFHintsProvider.java | 21 +++- 14 files changed, 400 insertions(+), 49 deletions(-) create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFParamsProviderMessages.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliProviderMessages.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ConnectionException.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/UnauthorizedException.java create mode 100644 vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java diff --git a/vscode-extensions/commons/commons-cf/pom.xml b/vscode-extensions/commons/commons-cf/pom.xml index a80147c9d..30e1c8e09 100644 --- a/vscode-extensions/commons/commons-cf/pom.xml +++ b/vscode-extensions/commons/commons-cf/pom.xml @@ -37,6 +37,13 @@ io.projectreactor.ipc reactor-netty ${reactor-netty} + + + + org.mockito + mockito-all + 1.10.19 + test \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java new file mode 100644 index 000000000..a6d565fea --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java @@ -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 checkConnection(Callable 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; + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFParamsProviderMessages.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFParamsProviderMessages.java new file mode 100644 index 000000000..234ef0e0e --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFParamsProviderMessages.java @@ -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(); + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java index e87be091f..4c6ba97c1 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java @@ -39,12 +39,13 @@ public class CFTarget { */ private final LoadingCache> buildpacksCache; private final LoadingCache> 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> servicesLoader = new CacheLoader>() { @Override @@ -73,24 +74,11 @@ public class CFTarget { } protected T runAndCheckForFailure(Callable 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() { diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java index d50434b5a..aad7aca89 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java @@ -28,9 +28,11 @@ public class CFTargetCache { private final CloudFoundryClientFactory clientFactory; private final ClientTimeouts timeouts; private final LoadingCache 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 loader = new CacheLoader() { @Override @@ -58,6 +61,10 @@ public class CFTargetCache { * for any other error encountered */ public synchronized List getOrCreate() throws NoTargetsException, Exception { + return callableContext.checkConnection(() -> doGetOrCreate()); + } + + protected synchronized List doGetOrCreate() throws NoTargetsException, Exception { List allParams = paramsProvider.getParams(); List 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) { diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java index e57856fd9..2a260af61 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java @@ -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; + } + } diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliProviderMessages.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliProviderMessages.java new file mode 100644 index 000000000..a27da45d3 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliProviderMessages.java @@ -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; + } +} \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java index f0e02e487..a1acb71aa 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java @@ -22,4 +22,6 @@ public interface ClientParamsProvider { * @throws ExecutionException if failure occurs while resolving params */ List getParams() throws NoTargetsException, ExecutionException; + + CFParamsProviderMessages getMessages(); } \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ConnectionException.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ConnectionException.java new file mode 100644 index 000000000..7123b897b --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ConnectionException.java @@ -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); + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/UnauthorizedException.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/UnauthorizedException.java new file mode 100644 index 000000000..7488edebb --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/UnauthorizedException.java @@ -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); + } +} diff --git a/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java index 8f6bd9181..9dad69f27 100644 --- a/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java +++ b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java @@ -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 buildPacks = target.getBuildpacks(); - assertTrue(!buildPacks.isEmpty()); - List 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 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)); } } diff --git a/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java new file mode 100644 index 000000000..16b09712e --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java @@ -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. + *

+ * 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); + } + +} diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java index 6a8b1fc12..d12fcefb4 100644 --- a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java @@ -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> toLookFor) { + for (Class 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 diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java index 0a143bad3..b26ca6c88 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java @@ -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> { public static final String EMPTY_VALUE = ""; @@ -59,12 +63,12 @@ public abstract class AbstractCFHintsProvider implements Callable