Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/fir 21173 add identity support #238

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ on:
description: 'Database - a new one will be created if not provided'
required: false
default: ''
engine:
description: 'Engine - a new one will be created if not provided'
required: false
account:
description: 'Account'
required: true
alexradzin marked this conversation as resolved.
Show resolved Hide resolved
default: 'developer'
environment:
description: 'Environment to run the tests against'
type: choice
Expand All @@ -21,6 +28,13 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Validate database and engine
if: ${{ (github.event.inputs.database == '') != (github.event.inputs.engine == '') }}
uses: actions/github-script@v3
with:
script: |
core.setFailed("Database and Engine parameters should be provided simultaneously")

- name: "Foresight: Collect Workflow Telemetry"
uses: runforesight/foresight-workflow-kit-action@v1
if: ${{ always() }}
Expand All @@ -35,25 +49,22 @@ jobs:
- name: Determine env variables
run: |
if [ "${{ github.event.inputs.environment }}" == 'staging' ]; then
echo "USERNAME=${{ secrets.FIREBOLT_USERNAME_STAGING }}" >> "$GITHUB_ENV"
echo "PASSWORD=${{ secrets.FIREBOLT_PASSWORD_STAGING }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_ID=${{ secrets.SERVICE_ACCOUNT_ID_STAGING }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_SECRET=${{ secrets.SERVICE_ACCOUNT_SECRET_STAGING }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_ID=${{ secrets.FIREBOLT_CLIENT_ID_STG_NEW_IDN }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_SECRET=${{ secrets.FIREBOLT_CLIENT_SECRET_STG_NEW_IDN }}" >> "$GITHUB_ENV"
else
echo "USERNAME=${{ secrets.FIREBOLT_USERNAME_DEV }}" >> "$GITHUB_ENV"
echo "PASSWORD=${{ secrets.FIREBOLT_PASSWORD_DEV }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_ID=${{ secrets.SERVICE_ACCOUNT_ID_DEV }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_SECRET=${{ secrets.SERVICE_ACCOUNT_SECRET_DEV }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_ID=${{ secrets.FIREBOLT_CLIENT_ID_NEW_IDN }}" >> "$GITHUB_ENV"
echo "SERVICE_ACCOUNT_SECRET=${{ secrets.FIREBOLT_CLIENT_SECRET_NEW_IDN }}" >> "$GITHUB_ENV"
fi

- name: Setup database and engine
id: setup
if: ${{ github.event.inputs.database == '' }}
uses: firebolt-db/integration-testing-setup@v1
uses: firebolt-db/integration-testing-setup@v2
with:
firebolt-username: ${{ env.USERNAME }}
firebolt-password: ${{ env.PASSWORD }}
firebolt-client-id: ${{ env.SERVICE_ACCOUNT_ID }}
firebolt-client-secret: ${{ env.SERVICE_ACCOUNT_SECRET }}
account: ${{ github.event.inputs.account }}
api-endpoint: "api.${{ github.event.inputs.environment }}.firebolt.io"
region: "us-east-1"
instance-type: "B2"

- name: Determine database name
Expand All @@ -65,8 +76,17 @@ jobs:
echo "database_name=${{ steps.setup.outputs.database_name }}" >> $GITHUB_OUTPUT
fi

- name: Determine engine name
id: find-engine-name
run: |
if ! [[ -z "${{ github.event.inputs.engine }}" ]]; then
echo "engine_name=${{ github.event.inputs.engine }}" >> $GITHUB_OUTPUT
else
echo "engine_name=${{ steps.setup.outputs.engine_name }}" >> $GITHUB_OUTPUT
fi

- name: Run integration tests
run: ./gradlew integrationTest -Ddb=${{ steps.find-database-name.outputs.database_name }} -Dapi=api.${{ github.event.inputs.environment }}.firebolt.io -Dpassword="${{ env.SERVICE_ACCOUNT_SECRET }}" -Duser="${{ env.SERVICE_ACCOUNT_ID }}"
run: ./gradlew integrationTest -Ddb=${{ steps.find-database-name.outputs.database_name }} -Denv=${{ github.event.inputs.environment }} -Dclient_secret="${{ env.SERVICE_ACCOUNT_SECRET }}" -Dclient_id="${{ env.SERVICE_ACCOUNT_ID }}" -Daccount="${{ github.event.inputs.account }}" -Dengine="${{ steps.find-engine-name.outputs.engine_name }}"

- name: "Foresight: Analyze Test Results"
uses: runforesight/foresight-test-kit-action@v1
Expand Down
89 changes: 76 additions & 13 deletions src/integrationTest/java/integration/ConnectionInfo.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,92 @@
package integration;

import lombok.Value;

import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import static java.lang.String.format;
import static java.lang.System.getProperty;
import static java.util.stream.Collectors.joining;

@Value
public class ConnectionInfo {
private static ConnectionInfo INSTANCE;
String password;
String user;
String api;
String database;
private static final String JDBC_URL_PREFIX = "jdbc:firebolt:";
private static volatile ConnectionInfo INSTANCE;
// principal and secret are used here instead of client_id and client_secret respectively as more common term also used in java security API.
private final String principal;
private final String secret;
private final String env;
private final String database;
private final String account;
private final String engine;

private ConnectionInfo() {
password = Optional.ofNullable(System.getProperty("password")).map(p -> p.replace("\"", "")).orElse(null);
user = Optional.ofNullable(System.getProperty("user")).map(u -> u.replace("\"", "")).orElse(null);
api = System.getProperty("api");
database = System.getProperty("db");
this(
getTrimmedProperty("client_id", "user"),
getTrimmedProperty("client_secret", "password"),
getProperty("env"),
getProperty("db"),
getProperty("account"),
getProperty("engine")
);
}

public ConnectionInfo(String principal, String secret, String env, String database, String account, String engine) {
this.principal = principal;
this.secret = secret;
this.env = env;
this.database = database;
this.account = account;
this.engine = engine;
}

public static ConnectionInfo getInstance() {
if (INSTANCE == null) {
INSTANCE = new ConnectionInfo();
synchronized (ConnectionInfo.class) {
if (INSTANCE == null) {
INSTANCE = new ConnectionInfo();
}
}
}
return INSTANCE;
}

private static String getTrimmedProperty(String name, String alias) {
return Optional.ofNullable(getProperty(name, getProperty(alias))).map(u -> u.replace("\"", "")).orElse(null);
}

public String getPrincipal() {
return principal;
}

public String getSecret() {
return secret;
}

public String getEnv() {
return env;
}

public String getDatabase() {
return database;
}

public String getAccount() {
return account;
}

public String getEngine() {
return engine;
}

public String toJdbcUrl() {
String params = Stream.of(param("env", env), param("engine", engine), param("account", account)).filter(Objects::nonNull).collect(joining("&"));
if (!params.isEmpty()) {
params = "?" + params;
}
return JDBC_URL_PREFIX + database + params;
}

private String param(String name, String value) {
return value == null ? null : format("%s=%s", name, value);
}
}
52 changes: 27 additions & 25 deletions src/integrationTest/java/integration/IntegrationTest.java
Original file line number Diff line number Diff line change
@@ -1,48 +1,47 @@
package integration;

import com.firebolt.jdbc.client.HttpClientConfig;
import com.google.common.io.Resources;
import lombok.CustomLog;
import lombok.SneakyThrows;
import org.junit.jupiter.api.TestInstance;

import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Optional;

import org.junit.jupiter.api.TestInstance;

import com.firebolt.jdbc.client.HttpClientConfig;
import com.google.common.io.Resources;

import lombok.CustomLog;
import lombok.SneakyThrows;
import static java.util.Objects.requireNonNull;

@CustomLog
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public abstract class IntegrationTest {

private static final String JDBC_URL_PREFIX = "jdbc:firebolt:";

protected Connection createLocalConnection(String queryParams) throws SQLException {
return DriverManager.getConnection(
"jdbc:firebolt://localhost" + "/" + integration.ConnectionInfo.getInstance().getDatabase()
+ queryParams,
integration.ConnectionInfo.getInstance().getUser(),
integration.ConnectionInfo.getInstance().getPassword());
JDBC_URL_PREFIX + integration.ConnectionInfo.getInstance().getDatabase()
+ queryParams + "&host=localhost" + getAccountParam(),
integration.ConnectionInfo.getInstance().getPrincipal(),
integration.ConnectionInfo.getInstance().getSecret());
}

protected Connection createConnection() throws SQLException {
return DriverManager.getConnection(
"jdbc:firebolt://" + integration.ConnectionInfo.getInstance().getApi() + "/"
+ integration.ConnectionInfo.getInstance().getDatabase(),
integration.ConnectionInfo.getInstance().getUser(),
integration.ConnectionInfo.getInstance().getPassword());
return DriverManager.getConnection(integration.ConnectionInfo.getInstance().toJdbcUrl(),
integration.ConnectionInfo.getInstance().getPrincipal(),
integration.ConnectionInfo.getInstance().getSecret());
}

protected Connection createConnection(String engine) throws SQLException {
return DriverManager.getConnection(
"jdbc:firebolt://" + integration.ConnectionInfo.getInstance().getApi() + "/"
+ integration.ConnectionInfo.getInstance().getDatabase()
+ Optional.ofNullable(engine).map(e -> "?engine=" + e).orElse(""),
integration.ConnectionInfo.getInstance().getUser(),
integration.ConnectionInfo.getInstance().getPassword());
ConnectionInfo current = integration.ConnectionInfo.getInstance();
ConnectionInfo updated = new ConnectionInfo(current.getPrincipal(), current.getSecret(),
current.getEnv(), current.getDatabase(), current.getAccount(), engine);
return DriverManager.getConnection(updated.toJdbcUrl(),
integration.ConnectionInfo.getInstance().getPrincipal(),
integration.ConnectionInfo.getInstance().getSecret());
}

protected void setParam(Connection connection, String name, String value) throws SQLException {
Expand All @@ -53,13 +52,13 @@ protected void setParam(Connection connection, String name, String value) throws

@SneakyThrows
protected void executeStatementFromFile(String path) {
executeStatementFromFile(path, null);
executeStatementFromFile(path, integration.ConnectionInfo.getInstance().getEngine());
}

@SneakyThrows
protected void executeStatementFromFile(String path, String engine) {
try (Connection connection = createConnection(engine); Statement statement = connection.createStatement()) {
String sql = Resources.toString(IntegrationTest.class.getResource(path), StandardCharsets.UTF_8);
String sql = Resources.toString(requireNonNull(IntegrationTest.class.getResource(path)), StandardCharsets.UTF_8);
statement.execute(sql);
}
}
Expand All @@ -70,4 +69,7 @@ protected void removeExistingClient() throws NoSuchFieldException, IllegalAccess
field.set(null, null);
}

private String getAccountParam() {
return "&account=" + integration.ConnectionInfo.getInstance().getAccount();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package integration;

import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

import java.io.IOException;

import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;

public abstract class MockWebServerAwareIntegrationTest extends IntegrationTest {
protected MockWebServer mockBackEnd;
private final int INIT_STATEMENTS_COUNT = 1; // The statement that validates that DB exists.

@BeforeEach
void setUp() throws IOException {
mockBackEnd = new MockWebServer();
mockBackEnd.start();
mockBackEnd.enqueue(new MockResponse().setResponseCode(200).setBody(format("database_name%nstring%n%s", System.getProperty("db"))));
}

@AfterEach
void tearDown() throws IOException {
mockBackEnd.close();
}


protected void assertMockBackendRequestsCount(int expected) {
assertEquals(INIT_STATEMENTS_COUNT + expected, mockBackEnd.getRequestCount());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package integration.tests;

import integration.IntegrationTest;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class NumericTypesTest extends IntegrationTest {
@Test
void shouldHaveCorrectInfo() throws SQLException {
try (Connection connection = this.createConnection(null);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT 3::decimal")) {
resultSet.next();
assertEquals(9, resultSet.getMetaData().getScale(1));
assertEquals(38, resultSet.getMetaData().getPrecision(1));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,9 @@ void shouldFailSQLInjectionAttempt() throws SQLException {
private QueryResult createExpectedResult(List<List<?>> expectedRows) {
return QueryResult.builder().databaseName(ConnectionInfo.getInstance().getDatabase())
.tableName("prepared_statement_test")
.columns(Arrays.asList(QueryResult.Column.builder().name("sales").type(FireboltDataType.BIG_INT).build(),
QueryResult.Column.builder().name("make").type(FireboltDataType.TEXT).build()))
.columns(
Arrays.asList(QueryResult.Column.builder().name("sales").type(FireboltDataType.BIG_INT).build(),
QueryResult.Column.builder().name("make").type(FireboltDataType.TEXT).build()))
.rows(expectedRows).build();

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ void afterEach() {
executeStatementFromFile("/statements/statement/cleanup.sql");
}

@Test
void shouldSelect1() throws SQLException {
try (Connection connection = this.createConnection(); Statement statement = connection.createStatement()) {
statement.executeQuery("SELECT 1;");
assertNotNull(statement.executeQuery("SELECT 1;"));
}
}

@Test
void shouldReuseStatementWhenNotCloseOnCompletion() throws SQLException {
try (Connection connection = this.createConnection(); Statement statement = connection.createStatement()) {
Expand Down
Loading