Skip to content

Commit

Permalink
some additional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexradzin committed Jul 24, 2023
1 parent f02f5b8 commit ffd00a6
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class FireboltObjectMapper {
public abstract class FireboltObjectMapper {
private static final ObjectMapper MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

private FireboltObjectMapper() {
}

public static com.fasterxml.jackson.databind.ObjectMapper getInstance() {
return MAPPER;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
package com.firebolt.jdbc.client.query;

import static com.firebolt.jdbc.exception.ExceptionType.UNAUTHORIZED;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;
import java.util.function.BiPredicate;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.firebolt.jdbc.client.FireboltClient;
import com.firebolt.jdbc.connection.FireboltConnection;
Expand All @@ -25,11 +13,32 @@
import com.firebolt.jdbc.util.CloseableUtil;
import com.firebolt.jdbc.util.PropertyUtil;
import com.google.common.collect.ImmutableMap;

import lombok.CustomLog;
import lombok.NonNull;
import okhttp3.*;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http2.StreamResetException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiPredicate;

import static com.firebolt.jdbc.connection.settings.FireboltQueryParameterKey.DEFAULT_FORMAT;
import static com.firebolt.jdbc.connection.settings.FireboltQueryParameterKey.OUTPUT_FORMAT;
import static com.firebolt.jdbc.exception.ExceptionType.UNAUTHORIZED;

@CustomLog
public class StatementClientImpl extends FireboltClient implements StatementClient {
Expand Down Expand Up @@ -222,13 +231,8 @@ private Map<String, String> getAllParameters(FireboltProperties fireboltProperti

private Optional<Pair<String, String>> getResponseFormatParameter(boolean isQuery, boolean isLocalDb) {
if (isQuery) {
if (isLocalDb) {
return Optional.of(new ImmutablePair<>(FireboltQueryParameterKey.DEFAULT_FORMAT.getKey(),
TAB_SEPARATED_WITH_NAMES_AND_TYPES_FORMAT));
} else {
return Optional.of(new ImmutablePair<>(FireboltQueryParameterKey.OUTPUT_FORMAT.getKey(),
TAB_SEPARATED_WITH_NAMES_AND_TYPES_FORMAT));
}
FireboltQueryParameterKey key = isLocalDb ? DEFAULT_FORMAT : OUTPUT_FORMAT;
return Optional.of(new ImmutablePair<>(key.getKey(), TAB_SEPARATED_WITH_NAMES_AND_TYPES_FORMAT));
}
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.firebolt.jdbc.exception;

public class FireboltUnsupportedOperationException extends UnsupportedOperationException {
import java.sql.SQLFeatureNotSupportedException;

public class FireboltUnsupportedOperationException extends SQLFeatureNotSupportedException {

public static final String OPERATION_NOT_SUPPORTED = "JDBC Operation not supported. Method: %s, Line: %d";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,12 @@ public int getDatabaseMinorVersion() throws SQLException {

@Override
public int getJDBCMajorVersion() throws SQLException {
return VersionUtil.getMajorDriverVersion();
return 4;
}

@Override
public int getJDBCMinorVersion() throws SQLException {
return VersionUtil.getDriverMinorVersion();
return 3;
}

@Override
Expand Down
29 changes: 16 additions & 13 deletions src/main/java/com/firebolt/jdbc/statement/FireboltStatement.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package com.firebolt.jdbc.statement;

import java.io.InputStream;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

import com.firebolt.jdbc.annotation.ExcludeFromJacocoGeneratedReport;
import com.firebolt.jdbc.annotation.NotImplemented;
import com.firebolt.jdbc.connection.FireboltConnection;
Expand All @@ -16,11 +9,23 @@
import com.firebolt.jdbc.exception.FireboltUnsupportedOperationException;
import com.firebolt.jdbc.service.FireboltStatementService;
import com.firebolt.jdbc.util.CloseableUtil;
import com.firebolt.jdbc.util.PropertyUtil;

import lombok.Builder;
import lombok.CustomLog;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@CustomLog
public class FireboltStatement implements Statement {

Expand All @@ -34,7 +39,7 @@ public class FireboltStatement implements Statement {
private volatile boolean isClosed = false;
private StatementResultWrapper currentStatementResult;
private StatementResultWrapper firstUnclosedStatementResult;
private int queryTimeout = -1;
private int queryTimeout = 0; // zero means that there is not limit
private String runningStatementId;

@Builder
Expand Down Expand Up @@ -438,7 +443,7 @@ public int getFetchSize() throws SQLException {
public void setFetchSize(int rows) throws SQLException {
validateStatementIsNotClosed();
if (rows < 0) {
throw new IllegalArgumentException("The number of rows cannot be less than 0");
throw new SQLException("The number of rows cannot be less than 0");
}
// Ignore
}
Expand Down Expand Up @@ -544,8 +549,6 @@ public boolean isPoolable() throws SQLException {
}

@Override
@NotImplemented
@ExcludeFromJacocoGeneratedReport
public void setPoolable(boolean poolable) throws SQLException {
throw new FireboltUnsupportedOperationException();
}
Expand Down
152 changes: 118 additions & 34 deletions src/test/java/com/firebolt/jdbc/client/FireboltClientTest.java
Original file line number Diff line number Diff line change
@@ -1,59 +1,143 @@
package com.firebolt.jdbc.client;

import static java.net.HttpURLConnection.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.firebolt.jdbc.connection.FireboltConnection;
import com.firebolt.jdbc.exception.ExceptionType;
import com.firebolt.jdbc.exception.FireboltException;

import com.firebolt.jdbc.resultset.compress.LZ4OutputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class FireboltClientTest {

@Test
void shouldThrowExceptionWhenResponseCodeIs401() {
Response response = mock(Response.class);
ResponseBody responseBody = mock(ResponseBody.class);
when(response.body()).thenReturn(responseBody);
when(response.code()).thenReturn(HTTP_UNAUTHORIZED);

FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
when(client.getConnection()).thenReturn(mock(FireboltConnection.class));
FireboltException exception = assertThrows(FireboltException.class,
() -> client.validateResponse("host", response, false));
assertEquals(ExceptionType.UNAUTHORIZED, exception.getType());
try (Response response = mockResponse(HTTP_UNAUTHORIZED)) {
FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
when(client.getConnection()).thenReturn(mock(FireboltConnection.class));
FireboltException exception = assertThrows(FireboltException.class,
() -> client.validateResponse("host", response, false));
assertEquals(ExceptionType.UNAUTHORIZED, exception.getType());
}
}

@Test
void shouldThrowExceptionWheResponseCodeIsOtherThan2XX() {
Response response = mock(Response.class);
ResponseBody responseBody = mock(ResponseBody.class);
when(response.body()).thenReturn(responseBody);
when(response.code()).thenReturn(HTTP_BAD_GATEWAY);

FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
when(client.getConnection()).thenReturn(mock(FireboltConnection.class));
FireboltException exception = assertThrows(FireboltException.class,
() -> client.validateResponse("host", response, false));
assertEquals(ExceptionType.ERROR, exception.getType());
try (Response response = mockResponse(HTTP_BAD_GATEWAY)) {
FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
when(client.getConnection()).thenReturn(mock(FireboltConnection.class));
FireboltException exception = assertThrows(FireboltException.class,
() -> client.validateResponse("host", response, false));
assertEquals(ExceptionType.ERROR, exception.getType());
}
}

@Test
void shouldNotThrowExceptionWhenResponseIs2XX() {
try (Response response = mockResponse(HTTP_OK)) {
FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
when(client.getConnection()).thenReturn(mock(FireboltConnection.class));
assertAll(() -> client.validateResponse("host", response, false));
}
}

@Test
void shouldThrowExceptionWhenEngineIsNotRunning() {
try (Response response = mockResponse(HTTP_UNAVAILABLE)) {
FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
assertEquals("Could not query Firebolt at my_host. The engine is not running.", assertThrows(FireboltException.class, () -> client.validateResponse("my_host", response, true)).getMessage());
}
}

@Test
void shouldFailWhenCannotReadResponseBody() throws IOException {
try (Response response = mock(Response.class)) {
when(response.code()).thenReturn(500);
ResponseBody responseBody = mock(ResponseBody.class);
when(response.body()).thenReturn(responseBody);
when(responseBody.bytes()).thenThrow(new IOException("ups"));
FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
FireboltException e = assertThrows(FireboltException.class, () -> client.validateResponse("the_host", response, true));
assertEquals(ExceptionType.ERROR, e.getType());
}
}

@Test
void cannotExtractCompressedErrorMessage() throws IOException {
try (Response response = mock(Response.class)) {
when(response.code()).thenReturn(HTTP_BAD_REQUEST);
ResponseBody responseBody = mock(ResponseBody.class);
when(response.body()).thenReturn(responseBody);
when(responseBody.bytes()).thenReturn("ups".getBytes()); // compressed error message that uses wrong format
FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
FireboltException e = assertThrows(FireboltException.class, () -> client.validateResponse("the_host", response, true));
assertEquals(ExceptionType.INVALID_REQUEST, e.getType());
assertTrue(e.getMessage().contains("ups")); // compressed error message is used as-is
}
}

@Test
void canExtractErrorMessage() throws IOException {
try (Response response = mock(Response.class)) {
when(response.code()).thenReturn(HTTP_NOT_FOUND);
ResponseBody responseBody = mock(ResponseBody.class);
when(response.body()).thenReturn(responseBody);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream compressedStream = new LZ4OutputStream(baos, 100);
compressedStream.write("Error happened".getBytes());
compressedStream.flush();
compressedStream.close();
when(responseBody.bytes()).thenReturn(baos.toByteArray()); // compressed error message

FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
FireboltException e = assertThrows(FireboltException.class, () -> client.validateResponse("the_host", response, true));
assertEquals(ExceptionType.RESOURCE_NOT_FOUND, e.getType());
assertTrue(e.getMessage().contains("Error happened")); // compressed error message is used as-is
}
}

@Test
void emptyResponseFromServer() throws IOException {
try (Response response = mock(Response.class)) {
when(response.code()).thenReturn(200);
when(response.body()).thenReturn(null);
OkHttpClient okHttpClient = mock(OkHttpClient.class);
Call call = mock();
when(call.execute()).thenReturn(response);
when(okHttpClient.newCall(any())).thenReturn(call);
FireboltClient client = new FireboltClient(okHttpClient, mock(), null, null, new ObjectMapper()) {};
assertEquals("Cannot get resource: the response from the server is empty", assertThrows(FireboltException.class, () -> client.getResource("http://foo", "foo", "token", String.class)).getMessage());
}
}

private Response mockResponse(int code) {
Response response = mock(Response.class);
ResponseBody responseBody = mock(ResponseBody.class);
when(response.body()).thenReturn(responseBody);
when(response.code()).thenReturn(HTTP_OK);

FireboltClient client = Mockito.mock(FireboltClient.class, Mockito.CALLS_REAL_METHODS);
when(client.getConnection()).thenReturn(mock(FireboltConnection.class));
assertAll(() -> client.validateResponse("host", response, false));
when(response.code()).thenReturn(code);
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,35 @@ class RetryInterceptorTest {

@ParameterizedTest
@ValueSource(ints = { HTTP_CLIENT_TIMEOUT, HTTP_BAD_GATEWAY, HTTP_UNAVAILABLE, HTTP_GATEWAY_TIMEOUT })
void shouldRetryOnRetryableResponseCode(int arg) throws IOException {
void shouldRetryOnRetryableResponseCodeWithoutTag(int responseCode) throws IOException {
shouldRetryOnRetryableResponseCode(responseCode, null);
}

/**
* The difference between with and without tag is just logging that very difficult to validate right now.
* At least we validate that additional logging does not cause any failure.
* @param responseCode response code
* @throws IOException on failure
*/
@ParameterizedTest
@ValueSource(ints = { HTTP_CLIENT_TIMEOUT, HTTP_BAD_GATEWAY, HTTP_UNAVAILABLE, HTTP_GATEWAY_TIMEOUT })
void shouldRetryOnRetryableResponseCodeWithTag(int responseCode) throws IOException {
shouldRetryOnRetryableResponseCode(responseCode, "my tag");
}

void shouldRetryOnRetryableResponseCode(int responseCode, Object tag) throws IOException {
int retries = 3;
RetryInterceptor retryInterceptor = new RetryInterceptor(retries);
Interceptor.Chain chain = mock(Interceptor.Chain.class);
Response response = mock(Response.class);
Call call = mock(Call.class);
Request request = mock(Request.class);
when(request.tag()).thenReturn(tag);

when(chain.request()).thenReturn(request);
when(chain.proceed(any(Request.class))).thenReturn(response);
when(response.isSuccessful()).thenReturn(false);
when(response.code()).thenReturn(arg);
when(response.code()).thenReturn(responseCode);
when(chain.call()).thenReturn(call);

retryInterceptor.intercept(chain);
Expand Down
Loading

0 comments on commit ffd00a6

Please sign in to comment.