amogh-jahagirdar commented on code in PR #10052: URL: https://github.com/apache/iceberg/pull/10052#discussion_r1550644916
########## core/src/test/java/org/apache/iceberg/rest/TestHTTPClient.java: ########## @@ -121,6 +128,92 @@ public void testHeadFailure() throws JsonProcessingException { testHttpMethodOnFailure(HttpMethod.HEAD); } + /** Tests that requests go via the proxy server in case the client is set up with one */ + @Test + public void testHttpClientProxyServerInteraction() throws IOException { + int proxyPort = 1070; + String proxyHostName = "localhost"; + try (ClientAndServer proxyServer = startClientAndServer(proxyPort); + RESTClient clientWithProxy = + HTTPClient.builder(ImmutableMap.of()) + .uri(URI) + .withProxy(proxyHostName, proxyPort) + .build()) { + // Set up the servers to match against a provided request + String path = "v1/config"; + + HttpRequest mockRequest = + request("/" + path).withMethod(HttpMethod.HEAD.name().toUpperCase(Locale.ROOT)); + + HttpResponse mockResponse = response().withStatusCode(200); + + mockServer.when(mockRequest).respond(mockResponse); + proxyServer.when(mockRequest).respond(mockResponse); + + restClient.head(path, ImmutableMap.of(), (onError) -> {}); + mockServer.verify(mockRequest, VerificationTimes.exactly(1)); + proxyServer.verify(mockRequest, VerificationTimes.never()); + + // Validate that the proxy server is hit only if the client is set up with one + clientWithProxy.head(path, ImmutableMap.of(), (onError) -> {}); + proxyServer.verify(mockRequest, VerificationTimes.exactly(1)); + } + } + + /** Negative test for basic username password authentication on the proxy server */ + @Test + public void testHttpClientProxyAuthenticationFailure() throws IOException { Review Comment: same as above, I don't think a test comment is required here, the name does a good job capturing what the test is validating ########## core/src/test/java/org/apache/iceberg/rest/TestHTTPClient.java: ########## @@ -121,6 +128,92 @@ public void testHeadFailure() throws JsonProcessingException { testHttpMethodOnFailure(HttpMethod.HEAD); } + /** Tests that requests go via the proxy server in case the client is set up with one */ + @Test + public void testHttpClientProxyServerInteraction() throws IOException { + int proxyPort = 1070; + String proxyHostName = "localhost"; + try (ClientAndServer proxyServer = startClientAndServer(proxyPort); + RESTClient clientWithProxy = + HTTPClient.builder(ImmutableMap.of()) + .uri(URI) + .withProxy(proxyHostName, proxyPort) + .build()) { + // Set up the servers to match against a provided request + String path = "v1/config"; + + HttpRequest mockRequest = + request("/" + path).withMethod(HttpMethod.HEAD.name().toUpperCase(Locale.ROOT)); + + HttpResponse mockResponse = response().withStatusCode(200); + + mockServer.when(mockRequest).respond(mockResponse); + proxyServer.when(mockRequest).respond(mockResponse); + + restClient.head(path, ImmutableMap.of(), (onError) -> {}); + mockServer.verify(mockRequest, VerificationTimes.exactly(1)); + proxyServer.verify(mockRequest, VerificationTimes.never()); + + // Validate that the proxy server is hit only if the client is set up with one + clientWithProxy.head(path, ImmutableMap.of(), (onError) -> {}); + proxyServer.verify(mockRequest, VerificationTimes.exactly(1)); + } + } + + /** Negative test for basic username password authentication on the proxy server */ + @Test + public void testHttpClientProxyAuthenticationFailure() throws IOException { + int proxyPort = 1070; + String proxyHostName = "localhost"; + String authorizedUsername = "test-username"; + String authorizedPassword = "test-password"; + String invalidPassword = "invalid-password"; + + // Let's build a basic credentials provider with invalid password + HttpHost proxy = new HttpHost(proxyHostName, proxyPort); + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope(proxy), + new UsernamePasswordCredentials(authorizedUsername, invalidPassword.toCharArray())); + + try (ClientAndServer proxyServer = + startClientAndServer( + new Configuration() + .proxyAuthenticationUsername(authorizedUsername) + .proxyAuthenticationPassword(authorizedPassword), + proxyPort); + // Inject the client with invalid credentials provider + RESTClient clientWithProxy = + HTTPClient.builder(ImmutableMap.of()) + .uri(URI) + .withProxy(proxyHostName, proxyPort) + .withProxyCredentialsProvider(credentialsProvider) + .build()) { + + ErrorHandler onError = + new ErrorHandler() { + @Override + public ErrorResponse parseResponse(int code, String responseBody) { + return null; + } + + @Override + public void accept(ErrorResponse errorResponse) { + throw new RuntimeException(errorResponse.message() + " - " + errorResponse.code()); + } + }; + + int expectedErrorCode = HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED; + String expectedErrorMsg = "Proxy Authentication Required"; + + // Validate that we hit a 407 Proxy Authentication Required exception Review Comment: same as above, I don;t think this inline comment is needed since it's apparent. ########## core/src/test/java/org/apache/iceberg/rest/TestHTTPClient.java: ########## @@ -121,6 +128,92 @@ public void testHeadFailure() throws JsonProcessingException { testHttpMethodOnFailure(HttpMethod.HEAD); } + /** Tests that requests go via the proxy server in case the client is set up with one */ Review Comment: Nit: While I personally like the idea of commenting tests typically in the project we don't add them and try to just write test names which are self evident in what they are testing. I think in this case just naming the test `testHttpClientProxyServer` is sufficient (no additional context needed I think for the happy case) ########## core/src/test/java/org/apache/iceberg/rest/TestHTTPClient.java: ########## @@ -121,6 +128,92 @@ public void testHeadFailure() throws JsonProcessingException { testHttpMethodOnFailure(HttpMethod.HEAD); } + /** Tests that requests go via the proxy server in case the client is set up with one */ + @Test + public void testHttpClientProxyServerInteraction() throws IOException { + int proxyPort = 1070; + String proxyHostName = "localhost"; + try (ClientAndServer proxyServer = startClientAndServer(proxyPort); + RESTClient clientWithProxy = + HTTPClient.builder(ImmutableMap.of()) + .uri(URI) + .withProxy(proxyHostName, proxyPort) + .build()) { + // Set up the servers to match against a provided request + String path = "v1/config"; + + HttpRequest mockRequest = + request("/" + path).withMethod(HttpMethod.HEAD.name().toUpperCase(Locale.ROOT)); + + HttpResponse mockResponse = response().withStatusCode(200); + + mockServer.when(mockRequest).respond(mockResponse); + proxyServer.when(mockRequest).respond(mockResponse); + + restClient.head(path, ImmutableMap.of(), (onError) -> {}); + mockServer.verify(mockRequest, VerificationTimes.exactly(1)); + proxyServer.verify(mockRequest, VerificationTimes.never()); Review Comment: I see the intent but I don't think we need this validation since other tests will validate this. The reason is, I think this verification becomes the equivalent of "restClient should be hitting mockServer directly, and there shouldn't be some other server in between" and the other tests will validate that just by leveraging the response that mockServer returns. ########## core/src/main/java/org/apache/iceberg/rest/HTTPClient.java: ########## @@ -468,6 +483,19 @@ public Builder uri(String path) { return this; } + public Builder withProxy(String hostname, int port) { + Preconditions.checkNotNull(hostname, "Invalid hostname for http client proxy: null"); + this.proxy = new HttpHost(hostname, port); + return this; + } + + public Builder withProxyCredentialsProvider(CredentialsProvider credentialsProvider) { + Preconditions.checkNotNull( + credentialsProvider, "Invalid credentials provider for http client proxy: null"); + this.credentialsProvider = credentialsProvider; Review Comment: I think `proxyCredentialsProvider` would be a better name. Also I think when `build` is called there should be a validation that if `proxyCredentialsProvider` is specified there is a proxy (it should not be the case that someone somehow specifies a proxyCredentialsProvider without having a proxy defined, if so build should fail). ########## core/src/test/java/org/apache/iceberg/rest/TestHTTPClient.java: ########## @@ -121,6 +128,92 @@ public void testHeadFailure() throws JsonProcessingException { testHttpMethodOnFailure(HttpMethod.HEAD); } + /** Tests that requests go via the proxy server in case the client is set up with one */ + @Test + public void testHttpClientProxyServerInteraction() throws IOException { + int proxyPort = 1070; + String proxyHostName = "localhost"; + try (ClientAndServer proxyServer = startClientAndServer(proxyPort); + RESTClient clientWithProxy = + HTTPClient.builder(ImmutableMap.of()) + .uri(URI) + .withProxy(proxyHostName, proxyPort) + .build()) { + // Set up the servers to match against a provided request + String path = "v1/config"; + + HttpRequest mockRequest = + request("/" + path).withMethod(HttpMethod.HEAD.name().toUpperCase(Locale.ROOT)); + + HttpResponse mockResponse = response().withStatusCode(200); + + mockServer.when(mockRequest).respond(mockResponse); + proxyServer.when(mockRequest).respond(mockResponse); + + restClient.head(path, ImmutableMap.of(), (onError) -> {}); + mockServer.verify(mockRequest, VerificationTimes.exactly(1)); + proxyServer.verify(mockRequest, VerificationTimes.never()); + + // Validate that the proxy server is hit only if the client is set up with one + clientWithProxy.head(path, ImmutableMap.of(), (onError) -> {}); + proxyServer.verify(mockRequest, VerificationTimes.exactly(1)); + } + } + + /** Negative test for basic username password authentication on the proxy server */ + @Test + public void testHttpClientProxyAuthenticationFailure() throws IOException { + int proxyPort = 1070; + String proxyHostName = "localhost"; + String authorizedUsername = "test-username"; + String authorizedPassword = "test-password"; + String invalidPassword = "invalid-password"; + + // Let's build a basic credentials provider with invalid password Review Comment: Nit: Inline comment not required here since I think it's apparent. also in comments typically we try and avoid first person/personal pronouns like "Let's" -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org For additional commands, e-mail: issues-h...@iceberg.apache.org