Skip to content

Commit d89ad56

Browse files
gopalldbclaude
andauthored
Add JDBC spec integration tests with fake service record-replay (#1212)
## Summary - Add 6 new fake service integration test classes (40 tests total) to close critical JDBC spec coverage gaps - All tests use the existing WireMock-based record-replay infrastructure in SEA mode - Tests are recorded against production and verified in replay mode (no token needed for CI) - WireMock stub recordings are committed alongside the test code ## Coverage Analysis ### Before (Baseline) | Metric | Value | |--------|-------| | Total Applicable JDBC Methods | 328 | | Methods with Integration Tests | 44 | | **Integration Test Coverage** | **13.4%** | ### After (This PR) | Metric | Value | |--------|-------| | Total Applicable JDBC Methods | 328 | | Methods with Integration Tests | ~70 | | **Integration Test Coverage** | **~21.3%** | ### New Methods Covered | Category | New Methods Tested | |----------|-------------------| | **Statement** | `getResultSet()`, `getUpdateCount()`, `getMoreResults()`, `addBatch(String)`, `executeBatch()`, `clearBatch()` | | **PreparedStatement** | `addBatch()`, `executeBatch()`, `clearBatch()`, method mismatch rejection for `execute(String)`, `executeQuery(String)`, `executeUpdate(String)` | | **ResultSet** | `isBeforeFirst()`, `isFirst()`, `getRow()`, `getType()`, `getConcurrency()`, `getFetchSize()`, `setFetchSize(int)`, `getFetchDirection()` | | **Connection** | `isClosed()`, `isValid(int)`, `getCatalog()`, `getSchema()` | | **Configuration** | `NonRowcountQueryPrefixes` behavior for `execute()` and `executeQuery()` with DML | ## Test plan - [x] All 40 tests pass in RECORD mode against production (e2-dogfood) - [x] All 40 tests pass in REPLAY mode (no network calls, no token needed) - [x] Compilation succeeds with `mvn test-compile` - [ ] Verify existing fake service tests still pass in replay mode NO_CHANGELOG=true --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dd52cc5 commit d89ad56

811 files changed

Lines changed: 31770 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/JDBC_METHOD_INVENTORY.md

Lines changed: 896 additions & 0 deletions
Large diffs are not rendered by default.

docs/JDBC_SPEC_COVERAGE_ANALYSIS.md

Lines changed: 1832 additions & 0 deletions
Large diffs are not rendered by default.

src/test/java/com/databricks/jdbc/api/impl/DatabricksResultSetTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1303,7 +1303,7 @@ void testGetObjectWithClassType_NullString_ReturnsNull() throws SQLException {
13031303
assertNull(resultSet.getObject(1, String.class));
13041304
assertTrue(resultSet.wasNull());
13051305
}
1306-
1306+
13071307
// --- Tests for TelemetryCollector caching in next() ---
13081308
private DatabricksResultSet getResultSetWithTelemetry() throws SQLException {
13091309
DatabricksConnection mockConnection = mock(DatabricksConnection.class);

src/test/java/com/databricks/jdbc/integration/fakeservice/tests/ConnectionIntegrationTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.databricks.jdbc.integration.fakeservice.tests;
22

33
import static com.databricks.jdbc.integration.IntegrationTestUtil.*;
4+
import static org.junit.jupiter.api.Assertions.*;
45
import static org.junit.jupiter.api.Assertions.assertThrows;
56

67
import com.databricks.jdbc.common.DatabricksJdbcUrlParams;
@@ -82,6 +83,50 @@ void testPATinOAuthTokenPassThrough() throws Exception {
8283
conn.close();
8384
}
8485

86+
// --- Connection management tests ---
87+
88+
@Test
89+
void testIsClosed_NewConnection() throws SQLException {
90+
Connection conn = getValidJDBCConnection();
91+
assertFalse(conn.isClosed(), "Newly created connection should not be closed");
92+
93+
conn.close();
94+
assertTrue(conn.isClosed(), "Connection should be closed after close()");
95+
}
96+
97+
@Test
98+
void testIsValid_ActiveConnection() throws SQLException {
99+
Connection conn = getValidJDBCConnection();
100+
101+
// isValid with a positive timeout should return true for an active connection
102+
assertTrue(conn.isValid(5), "Active connection should be valid");
103+
104+
conn.close();
105+
assertFalse(conn.isValid(5), "Closed connection should not be valid");
106+
}
107+
108+
@Test
109+
void testGetCatalog_ReturnsNonNull() throws SQLException {
110+
Connection conn = getValidJDBCConnection();
111+
112+
String catalog = conn.getCatalog();
113+
assertNotNull(catalog, "getCatalog() should return non-null for active connection");
114+
assertFalse(catalog.isEmpty(), "getCatalog() should return non-empty string");
115+
116+
conn.close();
117+
}
118+
119+
@Test
120+
void testGetSchema_ReturnsNonNull() throws SQLException {
121+
Connection conn = getValidJDBCConnection();
122+
123+
String schema = conn.getSchema();
124+
assertNotNull(schema, "getSchema() should return non-null for active connection");
125+
assertFalse(schema.isEmpty(), "getSchema() should return non-empty string");
126+
127+
conn.close();
128+
}
129+
85130
private Properties createConnectionProperties(Properties extraProps) {
86131
Properties connProps = new Properties();
87132
connProps.putAll(extraProps);

src/test/java/com/databricks/jdbc/integration/fakeservice/tests/ErrorHandlingIntegrationTests.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.databricks.jdbc.api.impl.DatabricksConnection;
77
import com.databricks.jdbc.common.DatabricksClientType;
88
import com.databricks.jdbc.exception.DatabricksSQLException;
9+
import com.databricks.jdbc.exception.DatabricksSQLFeatureNotImplementedException;
910
import com.databricks.jdbc.exception.DatabricksSQLFeatureNotSupportedException;
1011
import com.databricks.jdbc.integration.fakeservice.AbstractFakeServiceIntegrationTests;
1112
import com.databricks.jdbc.integration.fakeservice.FakeServiceConfigLoader;
@@ -136,4 +137,94 @@ void testCallingUnsupportedSQLFeature() {
136137
private void getConnection(String url) throws SQLException {
137138
DriverManager.getConnection(url, "username", "password");
138139
}
140+
141+
// --- Method mismatch tests (executeQuery with DML, executeUpdate with SELECT) ---
142+
143+
@Test
144+
void testExecuteQuery_WithInsert_ThrowsSQLException() throws SQLException {
145+
String tableName = "mismatch_insert_table";
146+
setupDatabaseTable(connection, tableName);
147+
148+
Statement stmt = connection.createStatement();
149+
String insertSQL =
150+
"INSERT INTO "
151+
+ getFullyQualifiedTableName(tableName)
152+
+ " (id, col1, col2) VALUES (1, 'a', 'b')";
153+
154+
DatabricksSQLException e =
155+
assertThrows(DatabricksSQLException.class, () -> stmt.executeQuery(insertSQL));
156+
assertTrue(
157+
e.getMessage().contains("ResultSet was expected but not generated"),
158+
"Error message should indicate no ResultSet was generated, got: " + e.getMessage());
159+
160+
deleteTable(connection, tableName);
161+
}
162+
163+
@Test
164+
void testExecuteQuery_WithUpdate_ThrowsSQLException() throws SQLException {
165+
String tableName = "mismatch_update_table";
166+
setupDatabaseTable(connection, tableName);
167+
insertTestData(connection, tableName);
168+
169+
Statement stmt = connection.createStatement();
170+
String updateSQL =
171+
"UPDATE " + getFullyQualifiedTableName(tableName) + " SET col1 = 'updated' WHERE id = 1";
172+
173+
DatabricksSQLException e =
174+
assertThrows(DatabricksSQLException.class, () -> stmt.executeQuery(updateSQL));
175+
assertTrue(
176+
e.getMessage().contains("ResultSet was expected but not generated"),
177+
"Error message should indicate no ResultSet was generated, got: " + e.getMessage());
178+
179+
deleteTable(connection, tableName);
180+
}
181+
182+
@Test
183+
void testExecuteQuery_WithDelete_ThrowsSQLException() throws SQLException {
184+
String tableName = "mismatch_delete_table";
185+
setupDatabaseTable(connection, tableName);
186+
insertTestData(connection, tableName);
187+
188+
Statement stmt = connection.createStatement();
189+
String deleteSQL = "DELETE FROM " + getFullyQualifiedTableName(tableName) + " WHERE id = 1";
190+
191+
DatabricksSQLException e =
192+
assertThrows(DatabricksSQLException.class, () -> stmt.executeQuery(deleteSQL));
193+
assertTrue(
194+
e.getMessage().contains("ResultSet was expected but not generated"),
195+
"Error message should indicate no ResultSet was generated, got: " + e.getMessage());
196+
197+
deleteTable(connection, tableName);
198+
}
199+
200+
@Test
201+
void testExecuteUpdate_WithSelect_ThrowsSQLException() throws SQLException {
202+
Statement stmt = connection.createStatement();
203+
204+
assertThrows(
205+
DatabricksSQLException.class,
206+
() -> stmt.executeUpdate("SELECT 1 AS num"),
207+
"executeUpdate() with SELECT should throw because result has no update count column");
208+
}
209+
210+
@Test
211+
void testPreparedStatement_StatementMethodsWithSQL_ThrowException() throws SQLException {
212+
String selectSQL = "SELECT 1";
213+
PreparedStatement pstmt = connection.prepareStatement(selectSQL);
214+
215+
assertThrows(
216+
DatabricksSQLFeatureNotImplementedException.class,
217+
() -> pstmt.executeQuery("SELECT 2"),
218+
"PreparedStatement.executeQuery(String) should throw");
219+
220+
assertThrows(
221+
DatabricksSQLFeatureNotImplementedException.class,
222+
() -> pstmt.executeUpdate("INSERT INTO dummy VALUES (1)"),
223+
"PreparedStatement.executeUpdate(String) should throw");
224+
225+
assertThrows(
226+
DatabricksSQLFeatureNotImplementedException.class,
227+
() -> pstmt.execute("SELECT 3"),
228+
"PreparedStatement.execute(String) should throw");
229+
}
139230
}

0 commit comments

Comments
 (0)