Skip to content

Commit c67751b

Browse files
feat: Add integration test as mandatory gate before publishing (#123)
- Add integration-test job to reusable-build-publish.yml workflow - Integration test must pass before JReleaser publishes to Maven Central - Add conditional JVM arguments for Java 9+ (--add-opens for Apache Arrow) - Add DataCloud secrets support for full end-to-end testing - Test covers driver loading, connection creation, and query execution - Prevents regression like gRPC NameResolver issue from being published
1 parent 28511e9 commit c67751b

File tree

7 files changed

+230
-1
lines changed

7 files changed

+230
-1
lines changed

.github/workflows/pr.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ jobs:
1212
publish: false
1313
secrets:
1414
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
15+
DATACLOUD_USERNAME: ${{ secrets.DATACLOUD_USERNAME }}
16+
DATACLOUD_PASSWORD: ${{ secrets.DATACLOUD_PASSWORD }}
17+
DATACLOUD_CLIENT_ID: ${{ secrets.DATACLOUD_CLIENT_ID }}
18+
DATACLOUD_CLIENT_SECRET: ${{ secrets.DATACLOUD_CLIENT_SECRET }}

.github/workflows/release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ jobs:
1717
signing_pass: ${{ secrets.GPG_SIGNING_KEY_PASSWORD }}
1818
publish_user: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
1919
publish_pass: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
20+
DATACLOUD_USERNAME: ${{ secrets.DATACLOUD_USERNAME }}
21+
DATACLOUD_PASSWORD: ${{ secrets.DATACLOUD_PASSWORD }}
22+
DATACLOUD_CLIENT_ID: ${{ secrets.DATACLOUD_CLIENT_ID }}
23+
DATACLOUD_CLIENT_SECRET: ${{ secrets.DATACLOUD_CLIENT_SECRET }}
2024

.github/workflows/reusable-build-publish.yml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ on:
2525
required: false
2626
signing_pub:
2727
required: false
28+
DATACLOUD_USERNAME:
29+
required: false
30+
DATACLOUD_PASSWORD:
31+
required: false
32+
DATACLOUD_CLIENT_ID:
33+
required: false
34+
DATACLOUD_CLIENT_SECRET:
35+
required: false
2836

2937
jobs:
3038
prepare:
@@ -120,8 +128,41 @@ jobs:
120128
build/hyperd/*.log
121129
retention-days: 5
122130

123-
publish-jreleaser:
131+
integration-test:
124132
needs: [ prepare, build ]
133+
runs-on: ubuntu-latest
134+
steps:
135+
- uses: actions/checkout@v4
136+
with:
137+
fetch-depth: 1
138+
- name: Setup Java
139+
uses: actions/setup-java@v4
140+
with:
141+
distribution: 'temurin'
142+
java-version: '17'
143+
- uses: gradle/actions/setup-gradle@v4
144+
- name: Build shaded JAR
145+
run: ./gradlew clean :jdbc:shadowJar
146+
- name: Run integration test
147+
run: |
148+
./gradlew :integration-test:runIntegrationTest \
149+
-Dtest.connection.url="jdbc:salesforce-datacloud://login.test2.pc-rnd.salesforce.com" \
150+
-Dtest.connection.userName="${{ secrets.DATACLOUD_USERNAME }}" \
151+
-Dtest.connection.password="${{ secrets.DATACLOUD_PASSWORD }}" \
152+
-Dtest.connection.clientId="${{ secrets.DATACLOUD_CLIENT_ID }}" \
153+
-Dtest.connection.clientSecret="${{ secrets.DATACLOUD_CLIENT_SECRET }}" \
154+
--info
155+
- name: Upload integration test results
156+
uses: actions/upload-artifact@v4
157+
if: always()
158+
with:
159+
name: integration-test-results
160+
path: |
161+
integration-test/build/reports/
162+
jdbc/build/libs/*-shaded.jar
163+
164+
publish-jreleaser:
165+
needs: [ prepare, build, integration-test ]
125166
if: ${{ inputs.publish }}
126167
runs-on: ubuntu-latest
127168
env:

.github/workflows/snapshot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ jobs:
1717
signing_pass: ${{ secrets.GPG_SIGNING_KEY_PASSWORD }}
1818
publish_user: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
1919
publish_pass: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
20+
DATACLOUD_USERNAME: ${{ secrets.DATACLOUD_USERNAME }}
21+
DATACLOUD_PASSWORD: ${{ secrets.DATACLOUD_PASSWORD }}
22+
DATACLOUD_CLIENT_ID: ${{ secrets.DATACLOUD_CLIENT_ID }}
23+
DATACLOUD_CLIENT_SECRET: ${{ secrets.DATACLOUD_CLIENT_SECRET }}

integration-test/build.gradle.kts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
plugins {
2+
id("java-conventions")
3+
alias(libs.plugins.lombok)
4+
}
5+
6+
description = "Integration test module for shaded JDBC driver"
7+
8+
dependencies {
9+
// Test framework
10+
testImplementation(platform(libs.junit.bom))
11+
testImplementation(libs.bundles.testing)
12+
13+
// Logging
14+
testImplementation(libs.slf4j.simple)
15+
}
16+
17+
// Configure test task to NOT run integration tests by default
18+
tasks.test {
19+
group = "verification"
20+
description = "Runs unit tests (integration tests are run separately with runIntegrationTest)"
21+
22+
// Exclude integration-tagged tests from regular test execution
23+
useJUnitPlatform {
24+
excludeTags("integration")
25+
}
26+
27+
// Exit with non-zero code on failure
28+
ignoreFailures = false
29+
}
30+
31+
// Integration test task - runs ONLY integration tests with proper configuration
32+
tasks.register("runIntegrationTest", Test::class) {
33+
group = "verification"
34+
description = "Runs integration tests against the shaded JDBC JAR (requires DATACLOUD credentials)"
35+
36+
// Copy configuration from main test task
37+
dependsOn(":jdbc:shadowJar")
38+
classpath += files(
39+
project(":jdbc").tasks.named("shadowJar").map { it.outputs.files.singleFile }
40+
)
41+
42+
// Pass system properties for test configuration
43+
systemProperty("test.connection.url", System.getProperty("test.connection.url", ""))
44+
systemProperty("test.connection.userName", System.getProperty("test.connection.userName", ""))
45+
systemProperty("test.connection.password", System.getProperty("test.connection.password", ""))
46+
systemProperty("test.connection.clientId", System.getProperty("test.connection.clientId", ""))
47+
systemProperty("test.connection.clientSecret", System.getProperty("test.connection.clientSecret", ""))
48+
49+
50+
// Only run integration-tagged tests
51+
useJUnitPlatform {
52+
includeTags("integration")
53+
}
54+
55+
ignoreFailures = false
56+
}
57+
58+
// Note: Integration test is run explicitly in CI/CD workflows, not as part of regular build
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* This file is part of https://github.com/forcedotcom/datacloud-jdbc which is released under the
3+
* Apache 2.0 license. See https://github.com/forcedotcom/datacloud-jdbc/blob/main/LICENSE.txt
4+
*/
5+
package com.salesforce.datacloud.jdbc.integration;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
import java.sql.Connection;
10+
import java.sql.Driver;
11+
import java.sql.DriverManager;
12+
import java.sql.ResultSet;
13+
import java.sql.SQLException;
14+
import java.sql.Statement;
15+
import java.util.Properties;
16+
import lombok.extern.slf4j.Slf4j;
17+
import org.junit.jupiter.api.BeforeAll;
18+
import org.junit.jupiter.api.MethodOrderer;
19+
import org.junit.jupiter.api.Order;
20+
import org.junit.jupiter.api.Tag;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.TestMethodOrder;
23+
24+
/**
25+
* Integration test that validates the shaded JDBC JAR works correctly.
26+
* This test focuses on the critical functionality that was broken by the service file regression:
27+
* - Driver loading and registration
28+
* - Connection establishment
29+
* - Basic query execution
30+
*
31+
* Note: This test requires JVM arguments for Apache Arrow memory access on Java 9+:
32+
* --add-opens=java.base/java.nio=com.salesforce.datacloud.shaded.org.apache.arrow.memory.core,ALL-UNNAMED
33+
* (These are automatically added when using the runIntegrationTest Gradle task for Java 9+)
34+
* Java 8 doesn't need these arguments as it doesn't have the module system.
35+
*/
36+
@Slf4j
37+
@Tag("integration")
38+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
39+
public class ShadedJarIntegrationTest {
40+
41+
private static final String DRIVER_CLASS = "com.salesforce.datacloud.jdbc.DataCloudJDBCDriver";
42+
43+
@BeforeAll
44+
static void setUp() {
45+
log.info("Starting Shaded JAR Integration Test Suite");
46+
}
47+
48+
/**
49+
* Test 1: Verify the JDBC driver can be loaded from the shaded JAR
50+
*/
51+
@Test
52+
@Order(1)
53+
void testDriverLoading() throws Exception {
54+
log.info("Test 1: Driver Loading");
55+
56+
Class<?> driverClass = Class.forName(DRIVER_CLASS);
57+
log.info("Driver class loaded: {}", driverClass.getName());
58+
59+
Driver driver = DriverManager.getDriver("jdbc:salesforce-datacloud:");
60+
log.info("Driver registered with DriverManager: {}", driver.getClass().getName());
61+
62+
// Verify driver accepts our URL format
63+
assertThat(driver.acceptsURL("jdbc:salesforce-datacloud://test.salesforce.com"))
64+
.isTrue();
65+
log.info("Driver accepts URL format");
66+
}
67+
68+
/**
69+
* Test 2: Verify connection and query execution (tests gRPC NameResolver and end-to-end functionality)
70+
* This is the critical test that would have caught the service file regression
71+
*/
72+
@Test
73+
@Order(2)
74+
void testConnectionAndQueryExecution() throws Exception {
75+
log.info("Test 2: Connection and Query Execution");
76+
77+
// Get connection details from system properties (for CI/CD secrets)
78+
String jdbcUrl = System.getProperty("test.connection.url");
79+
if (jdbcUrl == null || jdbcUrl.isEmpty()) {
80+
jdbcUrl = "jdbc:salesforce-datacloud://login.test2.pc-rnd.salesforce.com";
81+
}
82+
String userName = System.getProperty("test.connection.userName", "");
83+
String password = System.getProperty("test.connection.password", "");
84+
String clientId = System.getProperty("test.connection.clientId", "");
85+
String clientSecret = System.getProperty("test.connection.clientSecret", "");
86+
87+
Properties props = new Properties();
88+
props.put("dataspace", "default");
89+
if (!userName.isEmpty()) props.setProperty("userName", userName);
90+
if (!password.isEmpty()) props.setProperty("password", password);
91+
if (!clientId.isEmpty()) props.setProperty("clientId", clientId);
92+
if (!clientSecret.isEmpty()) props.setProperty("clientSecret", clientSecret);
93+
94+
log.info(" Attempting connection to: {}", jdbcUrl);
95+
96+
// Test connection creation and query execution - this will fail if gRPC service files are broken
97+
try (Connection conn = DriverManager.getConnection(jdbcUrl, props)) {
98+
log.info(" Connection established successfully");
99+
100+
// Verify connection is not closed
101+
assertThat(conn.isClosed()).isFalse();
102+
103+
// Test query execution
104+
try (Statement stmt = conn.createStatement()) {
105+
// Try a simple query first
106+
try (ResultSet rs = stmt.executeQuery("SELECT 1 as test_column")) {
107+
assertThat(rs.next()).isTrue();
108+
assertThat(rs.getInt("test_column")).isEqualTo(1);
109+
log.info(" Simple query executed successfully: {}", rs.getInt("test_column"));
110+
}
111+
}
112+
} catch (SQLException e) {
113+
log.error(" Connection failed: {}", e.getMessage());
114+
throw new AssertionError("Connection failed: " + e.getMessage(), e);
115+
}
116+
}
117+
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ include(":jdbc-proto")
1010
include(":jdbc-util")
1111
include(":jdbc-reference")
1212
include(":verification")
13+
include(":integration-test")

0 commit comments

Comments
 (0)