diff --git a/plugin/META-INF/MANIFEST.MF b/plugin/META-INF/MANIFEST.MF index dead985e5..9ff16a348 100644 --- a/plugin/META-INF/MANIFEST.MF +++ b/plugin/META-INF/MANIFEST.MF @@ -28,41 +28,46 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.31.0", org.apache.commons.logging;bundle-version="1.2.0", slf4j.api;bundle-version="2.0.13", org.apache.commons.lang3;bundle-version="3.14.0" -Bundle-Classpath: ., - target/dependency/annotations-2.28.26.jar, - target/dependency/apache-client-2.28.26.jar, - target/dependency/auth-2.28.26.jar, - target/dependency/aws-core-2.28.26.jar, - target/dependency/aws-json-protocol-2.28.26.jar, - target/dependency/checksums-2.28.26.jar, - target/dependency/checksums-spi-2.28.26.jar, - target/dependency/cognitoidentity-2.28.26.jar, - target/dependency/commons-codec-1.17.1.jar, - target/dependency/endpoints-spi-2.28.26.jar, - target/dependency/http-auth-2.28.26.jar, - target/dependency/http-auth-aws-2.28.26.jar, - target/dependency/http-auth-aws-eventstream-2.28.26.jar, - target/dependency/http-auth-spi-2.28.26.jar, - target/dependency/http-client-spi-2.28.26.jar, - target/dependency/httpclient-4.5.14.jar, - target/dependency/httpcore-4.4.16.jar, - target/dependency/identity-spi-2.28.26.jar, - target/dependency/jackson-annotations-2.17.2.jar, - target/dependency/jackson-core-2.17.2.jar, - target/dependency/jackson-databind-2.17.2.jar, - target/dependency/jakarta.inject-api-2.0.1.jar, - target/dependency/json-utils-2.28.26.jar, - target/dependency/maven-artifact-3.9.9.jar, - target/dependency/metrics-spi-2.28.26.jar, - target/dependency/netty-nio-client-2.28.26.jar, - target/dependency/nimbus-jose-jwt-9.41.2.jar, - target/dependency/profiles-2.28.26.jar, - target/dependency/protocol-core-2.28.26.jar, - target/dependency/reactive-streams-1.0.4.jar, - target/dependency/regions-2.28.26.jar, - target/dependency/retries-2.28.26.jar, - target/dependency/retries-spi-2.28.26.jar, - target/dependency/rxjava-3.1.5.jar, - target/dependency/sdk-core-2.28.26.jar, - target/dependency/third-party-jackson-core-2.28.26.jar, - target/dependency/utils-2.28.26.jar +Bundle-Classpath: target/classes/, + target/dependency/annotations.jar, + target/dependency/apache-client.jar, + target/dependency/auth.jar, + target/dependency/aws-core.jar, + target/dependency/aws-json-protocol.jar, + target/dependency/checksums-spi.jar, + target/dependency/checksums.jar, + target/dependency/cognitoidentity.jar, + target/dependency/commons-codec.jar, + target/dependency/delight-rhino-sandbox.jar, + target/dependency/endpoints-spi.jar, + target/dependency/http-auth-aws-eventstream.jar, + target/dependency/http-auth-aws.jar, + target/dependency/http-auth-spi.jar, + target/dependency/http-auth.jar, + target/dependency/http-client-spi.jar, + target/dependency/httpclient.jar, + target/dependency/httpcore.jar, + target/dependency/identity-spi.jar, + target/dependency/jackson-annotations.jar, + target/dependency/jackson-core.jar, + target/dependency/jackson-databind.jar, + target/dependency/jakarta.inject-api.jar, + target/dependency/jna-platform.jar, + target/dependency/jna.jar, + target/dependency/json-utils.jar, + target/dependency/maven-artifact.jar, + target/dependency/metrics-spi.jar, + target/dependency/netty-nio-client.jar, + target/dependency/nimbus-jose-jwt.jar, + target/dependency/profiles.jar, + target/dependency/protocol-core.jar, + target/dependency/proxy-vole.jar, + target/dependency/reactive-streams.jar, + target/dependency/regions.jar, + target/dependency/retries-spi.jar, + target/dependency/retries.jar, + target/dependency/rxjava.jar, + target/dependency/sdk-core.jar, + target/dependency/slf4j-api.jar, + target/dependency/third-party-jackson-core.jar, + target/dependency/utils.jar diff --git a/plugin/pom.xml b/plugin/pom.xml index 606bf886e..5ec2d85ad 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -42,21 +42,16 @@ - - io.reactivex.rxjava3 - rxjava - 3.1.5 - + + io.reactivex.rxjava3 + rxjava + 3.1.5 + jakarta.inject jakarta.inject-api 2.0.1 - - io.reactivex.rxjava3 - rxjava - 3.1.5 - com.fasterxml.jackson.core jackson-databind @@ -106,6 +101,11 @@ maven-artifact 3.9.9 + + org.bidib.com.github.markusbernhardt + proxy-vole + 1.1.6 + org.junit.jupiter junit-jupiter @@ -118,7 +118,7 @@ test - + src tst @@ -148,8 +148,24 @@ copy-dependencies + runtime + true ${project.build.directory}/dependency - io.reactivex,software.amazon.awssdk,com.fasterxml.jackson,com.nimbusds,jakarta.inject,commons-codec,org.apache.httpcomponents,org.reactivestreams,org.apache.maven + + io.reactivex, + software.amazon.awssdk, + com.fasterxml.jackson, + com.nimbusds,jakarta.inject, + commons-codec, + org.apache.httpcomponents, + org.reactivestreams, + org.apache.maven, + org.bidib.com.github.markusbernhardt, + net.java.dev.jna, + org.ini4j, + org.javadelight, + org.slf4j + diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java index 1c031f598..6308426dd 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java @@ -100,6 +100,8 @@ private String getCssForDarkTheme() { // Card themeMap.put(QChatCssVariable.CardBackground, cardBackgroundColor); + themeMap.put(QChatCssVariable.LineHeight, "1.25em"); + return getCss(themeMap); } @@ -150,6 +152,8 @@ private String getCssForLightTheme() { // Card themeMap.put(QChatCssVariable.CardBackground, cardBackgroundColor); + themeMap.put(QChatCssVariable.LineHeight, "1.25em"); + return getCss(themeMap); } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java index a57c7ce26..14bcd1b38 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java @@ -44,7 +44,10 @@ public enum QChatCssVariable { AlternateForeground("--mynah-color-alternate-reverse"), // Card - CardBackground("--mynah-card-bg"); + CardBackground("--mynah-card-bg"), + + // Line height + LineHeight("--mynah-line-height"); private String value; diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java index 9da462cfb..1ca8a6d36 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java @@ -44,10 +44,10 @@ public QLspConnectionProvider() throws IOException { @Override protected final void addEnvironmentVariables(final Map env) { - String httpsProxyPreference = ProxyUtil.getHttpsProxyUrl(); + String httpsProxyUrl = ProxyUtil.getHttpsProxyUrl(); String caCertPreference = Activator.getDefault().getPreferenceStore().getString(AmazonQPreferencePage.CA_CERT); - if (!StringUtils.isEmpty(httpsProxyPreference)) { - env.put("HTTPS_PROXY", httpsProxyPreference); + if (!StringUtils.isEmpty(httpsProxyUrl)) { + env.put("HTTPS_PROXY", httpsProxyUrl); } if (!StringUtils.isEmpty(caCertPreference)) { env.put("NODE_EXTRA_CA_CERTS", caCertPreference); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java index fc546669e..81a4c7b72 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java @@ -32,6 +32,7 @@ import software.aws.toolkits.eclipse.amazonq.util.HttpClientFactory; import software.aws.toolkits.eclipse.amazonq.util.ObjectMapperFactory; import software.aws.toolkits.eclipse.amazonq.util.PluginUtils; +import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils; import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification; import software.aws.toolkits.telemetry.TelemetryDefinitions.ManifestLocation; import software.aws.toolkits.telemetry.TelemetryDefinitions.Result; @@ -85,11 +86,25 @@ public Optional fetch() { return latestManifest; } catch (Exception e) { if (e.getCause() instanceof SSLHandshakeException) { - Display.getCurrent().asyncExec(() -> { - AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(), - Constants.IDE_SSL_HANDSHAKE_TITLE, - Constants.IDE_SSL_HANDSHAKE_BODY); - notification.open(); + ThreadingUtils.executeAsyncTask(() -> { + Display display = null; + while (display == null) { + display = Display.getDefault(); + if (display == null) { + try { + Thread.sleep(100); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + return; + } + } + } + display.asyncExec(() -> { + AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(), + Constants.IDE_SSL_HANDSHAKE_TITLE, + Constants.IDE_SSL_HANDSHAKE_BODY); + notification.open(); + }); }); } Activator.getLogger().error("Error fetching manifest from remote location", e); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/telemetry/service/DefaultTelemetryService.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/telemetry/service/DefaultTelemetryService.java index 3f1c9928d..0d813cab8 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/telemetry/service/DefaultTelemetryService.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/telemetry/service/DefaultTelemetryService.java @@ -14,6 +14,7 @@ import javax.net.ssl.SSLContext; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.SystemDefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; @@ -141,7 +142,7 @@ private static ToolkitTelemetryClient createDefaultTelemetryClient(final Region null, SSLConnectionSocketFactory.getDefaultHostnameVerifier() ); - var proxyUrl = ProxyUtil.getHttpsProxyUrl(); + var proxyUrl = ProxyUtil.getHttpsProxyUrlForEndpoint(endpoint); var httpClientBuilder = ApacheHttpClient.builder(); if (!StringUtils.isEmpty(proxyUrl)) { httpClientBuilder.proxyConfiguration(ProxyConfiguration.builder() @@ -151,7 +152,9 @@ private static ToolkitTelemetryClient createDefaultTelemetryClient(final Region httpClientBuilder.socketFactory(sslSocketFactory); - SdkHttpClient sdkHttpClient = httpClientBuilder.build(); + SdkHttpClient sdkHttpClient = httpClientBuilder + .credentialsProvider(new SystemDefaultCredentialsProvider()) + .build(); CognitoIdentityClient cognitoClient = CognitoIdentityClient.builder() .credentialsProvider(AnonymousCredentialsProvider.create()) .region(region) diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ProxyUtil.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ProxyUtil.java index a9b41a7ea..c1b777c10 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ProxyUtil.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ProxyUtil.java @@ -4,91 +4,203 @@ package software.aws.toolkits.eclipse.amazonq.util; import java.io.FileInputStream; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.ProxySelector; import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.security.KeyStore; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup; import org.eclipse.swt.widgets.Display; +import com.github.markusbernhardt.proxy.ProxySearch; + import software.amazon.awssdk.utils.StringUtils; import software.aws.toolkits.eclipse.amazonq.plugin.Activator; import software.aws.toolkits.eclipse.amazonq.preferences.AmazonQPreferencePage; public final class ProxyUtil { + private static final String DEFAULT_PROXY_ENDPOINT = "https://amazonaws.com"; + private static volatile boolean hasSeenInvalidProxyNotification; - private static boolean hasSeenInvalidProxyNotification; + private static ProxySelector proxySelector; - private ProxyUtil() { - // Prevent initialization - } + private ProxyUtil() { } // Prevent initialization public static String getHttpsProxyUrl() { - return getHttpsProxyUrl(System.getenv("HTTPS_PROXY"), - Activator.getDefault().getPreferenceStore().getString(AmazonQPreferencePage.HTTPS_PROXY)); + return getHttpsProxyUrlForEndpoint(DEFAULT_PROXY_ENDPOINT); } - protected static String getHttpsProxyUrl(final String envVarValue, final String prefValue) { - String httpsProxy = envVarValue; - if (!StringUtils.isEmpty(prefValue)) { - httpsProxy = prefValue; - } + public static String getHttpsProxyUrlForEndpoint(final String endpointUrl) { try { - if (!StringUtils.isEmpty(httpsProxy)) { - URI.create(httpsProxy); - } - } catch (IllegalArgumentException e) { - if (!hasSeenInvalidProxyNotification) { - hasSeenInvalidProxyNotification = true; - Display.getDefault().asyncExec(() -> { - AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(), - Constants.INVALID_PROXY_CONFIGURATION_TITLE, - Constants.INVALID_PROXY_CONFIGURATION_BODY); - notification.open(); - }); + String proxyPrefUrl = getHttpsProxyPreferenceUrl(); + if (!StringUtils.isEmpty(proxyPrefUrl)) { + return proxyPrefUrl; } + } catch (MalformedURLException e) { + showInvalidProxyNotification(); + return null; + } + + if (StringUtils.isEmpty(endpointUrl)) { + return null; + } + + URI endpointUri; + try { + endpointUri = new URI(endpointUrl); + } catch (URISyntaxException e) { + Activator.getLogger().error("Could not parse endpoint for proxy configuration: " + endpointUrl, e); return null; } - return httpsProxy; + + return getProxyUrlFromSelector(getProxySelector(), endpointUri); + } + + private static String getProxyUrlFromSelector(final ProxySelector proxySelector, final URI endpointUri) { + if (proxySelector == null) { + return null; + } + + var proxies = proxySelector.select(endpointUri); + if (proxies == null || proxies.isEmpty()) { + return null; + } + + return proxies.stream() + .filter(p -> p.type() != Proxy.Type.DIRECT) + .findFirst() + .map(proxy -> createProxyUrl(proxy, endpointUri)) + .orElseGet(() -> { + Activator.getLogger().info("No non-DIRECT proxies found for endpoint: " + endpointUri); + return null; + }); + } + + private static String createProxyUrl(final Proxy proxy, final URI endpointUri) { + if (!(proxy.address() instanceof InetSocketAddress addr)) { + return null; + } + + String scheme = determineProxyScheme(proxy.type(), endpointUri); + if (scheme == null) { + return null; + } + + String proxyUrl = String.format("%s://%s:%d", scheme, addr.getHostString(), addr.getPort()); + Activator.getLogger().info("Using proxy URL: " + proxyUrl + " for endpoint: " + endpointUri); + return proxyUrl; + } + + private static String determineProxyScheme(final Proxy.Type proxyType, final URI endpointUri) { + return switch (proxyType) { + case HTTP -> "http"; + case SOCKS -> "socks"; + default -> null; + }; + } + + protected static String getHttpsProxyPreferenceUrl() throws MalformedURLException { + String prefValue = Activator.getDefault().getPreferenceStore() + .getString(AmazonQPreferencePage.HTTPS_PROXY); + + if (StringUtils.isEmpty(prefValue)) { + return null; + } + + new URL(prefValue); // Validate URL format + return prefValue; + } + + private static void showInvalidProxyNotification() { + if (!hasSeenInvalidProxyNotification) { + hasSeenInvalidProxyNotification = true; + Display.getDefault().asyncExec(() -> { + AbstractNotificationPopup notification = new ToolkitNotification( + Display.getCurrent(), + Constants.INVALID_PROXY_CONFIGURATION_TITLE, + Constants.INVALID_PROXY_CONFIGURATION_BODY + ); + notification.open(); + }); + } } public static SSLContext getCustomSslContext() { + String customCertPath = getCustomCertPath(); + if (StringUtils.isEmpty(customCertPath)) { + return null; + } + try { - String customCertPath = System.getenv("NODE_EXTRA_CA_CERTS"); - String caCertPreference = Activator.getDefault().getPreferenceStore().getString(AmazonQPreferencePage.CA_CERT); - if (!StringUtils.isEmpty(caCertPreference)) { - customCertPath = caCertPreference; - } + return createSslContextWithCustomCert(customCertPath); + } catch (Exception e) { + Activator.getLogger().error("Failed to set up SSL context. Additional certs will not be used.", e); + return null; + } + } + + private static String getCustomCertPath() { + String caCertPreference = Activator.getDefault().getPreferenceStore().getString(AmazonQPreferencePage.CA_CERT); + return !StringUtils.isEmpty(caCertPreference) ? caCertPreference : System.getenv("NODE_EXTRA_CA_CERTS"); + } - if (customCertPath != null && !customCertPath.isEmpty()) { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - X509Certificate cert; + private static SSLContext createSslContextWithCustomCert(final String certPath) throws Exception { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); - try (FileInputStream fis = new FileInputStream(customCertPath)) { - cert = (X509Certificate) certificateFactory.generateCertificate(fis); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + X509TrustManager xtm = (X509TrustManager) tm; + for (X509Certificate cert : xtm.getAcceptedIssuers()) { + keyStore.setCertificateEntry(cert.getSubjectX500Principal().getName(), cert); } + } + } - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, null); - keyStore.setCertificateEntry("custom-cert", cert); + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + X509Certificate cert; - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(keyStore); + try (FileInputStream fis = new FileInputStream(certPath)) { + cert = (X509Certificate) certificateFactory.generateCertificate(fis); + } - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(null, tmf.getTrustManagers(), null); - Activator.getLogger().info("Picked up custom CA cert."); + keyStore.setCertificateEntry("custom-cert", cert); - return sslContext; + TrustManagerFactory customTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + customTmf.init(keyStore); + + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, customTmf.getTrustManagers(), null); + Activator.getLogger().info("Picked up custom CA cert."); + + return sslContext; + } + + static synchronized ProxySelector getProxySelector() { + if (proxySelector == null) { + ProxySearch proxySearch = new ProxySearch(); + proxySearch.addStrategy(ProxySearch.Strategy.ENV_VAR); + proxySearch.addStrategy(ProxySearch.Strategy.JAVA); + proxySearch.addStrategy(ProxySearch.Strategy.OS_DEFAULT); + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + proxySearch.addStrategy(ProxySearch.Strategy.IE); } - } catch (Exception e) { - Activator.getLogger().error("Failed to set up SSL context. Additional certs will not be used.", e); + proxySelector = proxySearch.getProxySelector(); } - return null; + return proxySelector; } - } diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java index 6ec137cfc..45ae883ef 100644 --- a/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java @@ -14,7 +14,7 @@ public class QChatCssVariableTest { @Test void testEnumValues() { - assertEquals(27, QChatCssVariable.values().length); + assertEquals(28, QChatCssVariable.values().length); } @Test @@ -72,6 +72,11 @@ void testCardValues() { assertEquals("--mynah-card-bg", QChatCssVariable.CardBackground.getValue()); } + @Test + void testLineHeighValues() { + assertEquals("--mynah-line-height", QChatCssVariable.LineHeight.getValue()); + } + @Test void testInvalidEnum() { assertThrows(IllegalArgumentException.class, () -> QChatCssVariable.valueOf("NonExistentVariable")); diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/ProxyUtilTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/ProxyUtilTest.java index c9e5e983e..1525172eb 100644 --- a/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/ProxyUtilTest.java +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/ProxyUtilTest.java @@ -3,58 +3,134 @@ package software.aws.toolkits.eclipse.amazonq.util; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.MockedStatic; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.swt.widgets.Display; + +import software.aws.toolkits.eclipse.amazonq.extensions.implementation.ActivatorStaticMockExtension; +import software.aws.toolkits.eclipse.amazonq.plugin.Activator; +import software.aws.toolkits.eclipse.amazonq.preferences.AmazonQPreferencePage; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.util.Arrays; +import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; -import org.eclipse.swt.widgets.Display; +public final class ProxyUtilTest { + + @RegisterExtension + private static ActivatorStaticMockExtension activatorStaticMockExtension = new ActivatorStaticMockExtension(); -class ProxyUtilTest { + private IPreferenceStore preferenceStore; + private ProxySelector proxySelector; + + @BeforeEach + void setUp() { + preferenceStore = mock(IPreferenceStore.class); + proxySelector = mock(ProxySelector.class); + + Activator activatorMock = activatorStaticMockExtension.getMock(Activator.class); + when(activatorMock.getPreferenceStore()).thenReturn(preferenceStore); + } @Test - void testNoProxyConfigReturnsNull() { - assertEquals(null, ProxyUtil.getHttpsProxyUrl(null, null)); + void testNoProxyConfigurationReturnsNull() { + when(preferenceStore.getString(AmazonQPreferencePage.HTTPS_PROXY)).thenReturn(""); + try (MockedStatic proxyUtilMock = mockStatic(ProxyUtil.class, CALLS_REAL_METHODS)) { + proxyUtilMock.when(ProxyUtil::getProxySelector).thenReturn(proxySelector); + when(proxySelector.select(any())).thenReturn(Collections.emptyList()); + + assertNull(ProxyUtil.getHttpsProxyUrl()); + } } @Test - void testEnvVarProxyUrl() { - String mockUrl = "http://foo.com:8888"; - assertEquals(mockUrl, ProxyUtil.getHttpsProxyUrl(mockUrl, null)); + void testPreferenceProxyUrlTakesPrecedence() { + String preferenceUrl = "http://preference.proxy:8888"; + when(preferenceStore.getString(AmazonQPreferencePage.HTTPS_PROXY)).thenReturn(preferenceUrl); + + assertEquals(preferenceUrl, ProxyUtil.getHttpsProxyUrlForEndpoint("https://foo.com")); } @Test - void testPreferenceProxyUrl() { - String mockUrl = "http://foo.com:8888"; - assertEquals(mockUrl, ProxyUtil.getHttpsProxyUrl(null, mockUrl)); + void testSystemProxyConfiguration() { + when(preferenceStore.getString(AmazonQPreferencePage.HTTPS_PROXY)).thenReturn(""); + + try (MockedStatic proxyUtilMock = mockStatic(ProxyUtil.class, CALLS_REAL_METHODS)) { + proxyUtilMock.when(ProxyUtil::getProxySelector).thenReturn(proxySelector); + + Proxy httpProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.example.com", 8080)); + when(proxySelector.select(any())).thenReturn(Arrays.asList(httpProxy)); + + assertEquals("http://proxy.example.com:8080", ProxyUtil.getHttpsProxyUrlForEndpoint("https://foo.com")); + } } @Test - void testPreferenceProxyUrlPrecedence() { - String mockUrl = "http://foo.com:8888"; - assertEquals(mockUrl, ProxyUtil.getHttpsProxyUrl("http://bar.com:8888", mockUrl)); + void testSocksProxyConfiguration() { + when(preferenceStore.getString(AmazonQPreferencePage.HTTPS_PROXY)).thenReturn(""); + + try (MockedStatic proxyUtilMock = mockStatic(ProxyUtil.class, CALLS_REAL_METHODS)) { + proxyUtilMock.when(ProxyUtil::getProxySelector).thenReturn(proxySelector); + + Proxy socksProxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("socks.example.com", 1080)); + when(proxySelector.select(any())).thenReturn(Arrays.asList(socksProxy)); + + assertEquals("socks://socks.example.com:1080", ProxyUtil.getHttpsProxyUrlForEndpoint("https://foo.com")); + } } @Test - void testEnvVarInvalidProxyUrl() { + void testInvalidPreferenceProxyUrl() { + when(preferenceStore.getString(AmazonQPreferencePage.HTTPS_PROXY)).thenReturn("invalid:url"); + try (MockedStatic displayMock = mockStatic(Display.class)) { Display mockDisplay = mock(Display.class); displayMock.when(Display::getDefault).thenReturn(mockDisplay); - String mockUrl = "127.0.0.1:8000"; - assertEquals(null, ProxyUtil.getHttpsProxyUrl(mockUrl, null)); + when(Display.getCurrent()).thenReturn(mockDisplay); + + assertNull(ProxyUtil.getHttpsProxyUrlForEndpoint("https://foo.com")); } } @Test - void testPreferenceInvalidProxyUrl() { - try (MockedStatic displayMock = mockStatic(Display.class)) { - Display mockDisplay = mock(Display.class); - displayMock.when(Display::getDefault).thenReturn(mockDisplay); - String mockUrl = "127.0.0.1:8000"; - assertEquals(null, ProxyUtil.getHttpsProxyUrl(null, mockUrl)); + void testDirectProxyReturnsNull() { + when(preferenceStore.getString(AmazonQPreferencePage.HTTPS_PROXY)).thenReturn(""); + + try (MockedStatic proxyUtilMock = mockStatic(ProxyUtil.class, CALLS_REAL_METHODS)) { + proxyUtilMock.when(ProxyUtil::getProxySelector).thenReturn(proxySelector); + + Proxy directProxy = Proxy.NO_PROXY; + when(proxySelector.select(any())).thenReturn(Arrays.asList(directProxy)); + + assertNull(ProxyUtil.getHttpsProxyUrlForEndpoint("https://foo.com")); } } + @Test + void testPreservesEndpointScheme() { + when(preferenceStore.getString(AmazonQPreferencePage.HTTPS_PROXY)).thenReturn(""); + + try (MockedStatic proxyUtilMock = mockStatic(ProxyUtil.class, CALLS_REAL_METHODS)) { + proxyUtilMock.when(ProxyUtil::getProxySelector).thenReturn(proxySelector); + + Proxy httpProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.example.com", 8080)); + when(proxySelector.select(any())).thenReturn(Arrays.asList(httpProxy)); + + assertEquals("http://proxy.example.com:8080", ProxyUtil.getHttpsProxyUrlForEndpoint("http://foo.com")); + } + } } + diff --git a/pom.xml b/pom.xml index ad3e8b64a..0190ac67f 100644 --- a/pom.xml +++ b/pom.xml @@ -27,11 +27,7 @@ p2 https://download.eclipse.org/releases/2024-06 - - lsp4e - p2 - http://download.eclipse.org/lsp4e/releases/latest/ - +