Skip to content

Commit 92a8275

Browse files
committed
[Kernel][UC] Add comprehensive tests for UCManagedCommitClientAdapter with test doubles
1 parent a17ef5a commit 92a8275

File tree

1 file changed

+206
-21
lines changed

1 file changed

+206
-21
lines changed

kernel-spark/src/test/scala/io/delta/kernel/spark/catalog/UCManagedCommitClientAdapterSuite.scala

Lines changed: 206 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,223 @@
1616

1717
package io.delta.kernel.spark.catalog
1818

19+
import io.delta.kernel.Snapshot
20+
import io.delta.kernel.engine.Engine
21+
import io.delta.kernel.unitycatalog.UCCatalogManagedClient
22+
import io.delta.storage.commit.{Commit, GetCommitsResponse}
23+
import io.delta.storage.commit.uccommitcoordinator.UCClient
24+
import java.io.IOException
25+
import java.net.URI
26+
import java.util.Optional
1927
import org.scalatest.funsuite.AnyFunSuite
2028

2129
/**
22-
* Basic validation tests for [[UCManagedCommitClientAdapter]].
30+
* Tests for [[UCManagedCommitClientAdapter]].
2331
*
24-
* Note: This adapter is a simple delegation wrapper with no business logic.
25-
* Comprehensive testing happens through integration tests with real UC tables.
26-
* These tests just verify basic construction requirements.
32+
* Uses simple test doubles instead of mocking frameworks to validate
33+
* adapter behavior including error handling and edge cases.
2734
*/
2835
class UCManagedCommitClientAdapterSuite extends AnyFunSuite {
2936

30-
test("constructor validates non-null parameters") {
31-
// The constructor uses requireNonNull which throws NullPointerException
32-
// for null arguments. We verify this requirement is enforced.
37+
// Test double for UCClient
38+
class TestUCClient extends UCClient {
39+
var closeCalled = false
40+
var getCommitsCalled = 0
41+
var latestVersionToReturn: Long = 5
42+
var shouldThrowOnGetCommits = false
43+
var commitsToReturn: java.util.List[Commit] = java.util.Collections.emptyList()
3344

34-
// This test documents the contract without needing to instantiate
35-
// actual UC clients (which would require test infrastructure)
36-
assert(classOf[UCManagedCommitClientAdapter] != null)
45+
override def getMetastoreId(): String = "test-metastore"
3746

38-
// The adapter implements ManagedCommitClient interface
39-
val interfaces = classOf[UCManagedCommitClientAdapter].getInterfaces
40-
assert(interfaces.exists(_.getName == "io.delta.kernel.spark.catalog.ManagedCommitClient"))
47+
override def commit(
48+
tableId: String,
49+
tableUri: URI,
50+
commit: Optional[Commit],
51+
lastKnownBackfilledVersion: Optional[java.lang.Long],
52+
disown: Boolean,
53+
newMetadata: Optional[io.delta.storage.commit.actions.AbstractMetadata],
54+
newProtocol: Optional[io.delta.storage.commit.actions.AbstractProtocol]): Unit = {
55+
throw new UnsupportedOperationException("Not used in adapter tests")
56+
}
57+
58+
override def getCommits(
59+
tableId: String,
60+
tableUri: URI,
61+
startVersion: Optional[java.lang.Long],
62+
endVersion: Optional[java.lang.Long]): GetCommitsResponse = {
63+
getCommitsCalled += 1
64+
if (shouldThrowOnGetCommits) {
65+
throw new IOException("Test exception")
66+
}
67+
new GetCommitsResponse(commitsToReturn, latestVersionToReturn)
68+
}
69+
70+
override def close(): Unit = {
71+
closeCalled = true
72+
}
73+
}
74+
75+
// Test double for UCCatalogManagedClient
76+
class TestUCCatalogManagedClient(client: UCClient) extends UCCatalogManagedClient(client) {
77+
var loadSnapshotCalled = 0
78+
var lastEnginePassedIn: Engine = _
79+
var lastTableIdPassedIn: String = _
80+
var lastTablePathPassedIn: String = _
81+
var lastVersionPassedIn: Optional[java.lang.Long] = _
82+
var lastTimestampPassedIn: Optional[java.lang.Long] = _
83+
var snapshotToReturn: Snapshot = _
84+
85+
override def loadSnapshot(
86+
engine: Engine,
87+
ucTableId: String,
88+
tablePath: String,
89+
versionOpt: Optional[java.lang.Long],
90+
timestampOpt: Optional[java.lang.Long]): Snapshot = {
91+
loadSnapshotCalled += 1
92+
lastEnginePassedIn = engine
93+
lastTableIdPassedIn = ucTableId
94+
lastTablePathPassedIn = tablePath
95+
lastVersionPassedIn = versionOpt
96+
lastTimestampPassedIn = timestampOpt
97+
snapshotToReturn
98+
}
99+
}
100+
101+
102+
test("constructor validates non-null ucClient") {
103+
val testUCClient = new TestUCClient()
104+
assertThrows[NullPointerException] {
105+
new UCManagedCommitClientAdapter(null, testUCClient, "/tmp/test")
106+
}
107+
}
108+
109+
test("constructor validates non-null rawUCClient") {
110+
val testUCClient = new TestUCClient()
111+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
112+
assertThrows[NullPointerException] {
113+
new UCManagedCommitClientAdapter(testUCCatalogClient, null, "/tmp/test")
114+
}
115+
}
116+
117+
test("constructor validates non-null tablePath") {
118+
val testUCClient = new TestUCClient()
119+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
120+
assertThrows[NullPointerException] {
121+
new UCManagedCommitClientAdapter(testUCCatalogClient, testUCClient, null)
122+
}
123+
}
124+
125+
test("getSnapshot delegates to UCCatalogManagedClient with correct parameters") {
126+
val testUCClient = new TestUCClient()
127+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
128+
// Use null as snapshot - we're only testing delegation, not snapshot behavior
129+
testUCCatalogClient.snapshotToReturn = null
130+
131+
val adapter = new UCManagedCommitClientAdapter(
132+
testUCCatalogClient,
133+
testUCClient,
134+
"/tmp/test/table")
135+
136+
val mockEngine = null.asInstanceOf[Engine] // Not used by test double
137+
adapter.getSnapshot(
138+
mockEngine,
139+
"test-table-id",
140+
"/tmp/test/table",
141+
Optional.empty(),
142+
Optional.empty())
143+
144+
// Verify delegation happened with correct parameters
145+
assert(testUCCatalogClient.loadSnapshotCalled == 1)
146+
assert(testUCCatalogClient.lastTableIdPassedIn == "test-table-id")
147+
assert(testUCCatalogClient.lastTablePathPassedIn == "/tmp/test/table")
148+
assert(!testUCCatalogClient.lastVersionPassedIn.isPresent)
149+
assert(!testUCCatalogClient.lastTimestampPassedIn.isPresent)
150+
}
151+
152+
test("versionExists returns true when version exists") {
153+
val testUCClient = new TestUCClient()
154+
testUCClient.latestVersionToReturn = 10
155+
testUCClient.commitsToReturn = java.util.Collections.emptyList()
156+
157+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
158+
val adapter = new UCManagedCommitClientAdapter(
159+
testUCCatalogClient,
160+
testUCClient,
161+
"/tmp/test/table")
162+
163+
assert(adapter.versionExists("test-table-id", 5))
164+
assert(testUCClient.getCommitsCalled == 1)
165+
}
166+
167+
test("versionExists returns false when UCClient throws exception") {
168+
val testUCClient = new TestUCClient()
169+
testUCClient.shouldThrowOnGetCommits = true
170+
171+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
172+
val adapter = new UCManagedCommitClientAdapter(
173+
testUCCatalogClient,
174+
testUCClient,
175+
"/tmp/test/table")
176+
177+
assert(!adapter.versionExists("test-table-id", 5))
178+
assert(testUCClient.getCommitsCalled == 1)
179+
}
180+
181+
test("getLatestVersion returns latestTableVersion from UC") {
182+
val testUCClient = new TestUCClient()
183+
testUCClient.latestVersionToReturn = 42
184+
185+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
186+
val adapter = new UCManagedCommitClientAdapter(
187+
testUCCatalogClient,
188+
testUCClient,
189+
"/tmp/test/table")
190+
191+
val result = adapter.getLatestVersion("test-table-id")
192+
assert(result == 42)
193+
assert(testUCClient.getCommitsCalled == 1)
194+
}
195+
196+
test("getLatestVersion converts -1 to 0 for newly created tables") {
197+
val testUCClient = new TestUCClient()
198+
testUCClient.latestVersionToReturn = -1 // UC returns -1 when only 0.json exists
199+
200+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
201+
val adapter = new UCManagedCommitClientAdapter(
202+
testUCCatalogClient,
203+
testUCClient,
204+
"/tmp/test/table")
205+
206+
val result = adapter.getLatestVersion("test-table-id")
207+
assert(result == 0) // Should convert -1 to 0
208+
}
209+
210+
test("getLatestVersion throws RuntimeException when UCClient fails") {
211+
val testUCClient = new TestUCClient()
212+
testUCClient.shouldThrowOnGetCommits = true
213+
214+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
215+
val adapter = new UCManagedCommitClientAdapter(
216+
testUCCatalogClient,
217+
testUCClient,
218+
"/tmp/test/table")
219+
220+
val exception = intercept[RuntimeException] {
221+
adapter.getLatestVersion("test-table-id")
222+
}
223+
assert(exception.getMessage.contains("Failed to get latest version"))
41224
}
42225

43-
test("adapter class has expected methods") {
44-
val methods = classOf[UCManagedCommitClientAdapter].getDeclaredMethods
45-
val methodNames = methods.map(_.getName).toSet
226+
test("close delegates to UCClient") {
227+
val testUCClient = new TestUCClient()
228+
val testUCCatalogClient = new TestUCCatalogManagedClient(testUCClient)
229+
val adapter = new UCManagedCommitClientAdapter(
230+
testUCCatalogClient,
231+
testUCClient,
232+
"/tmp/test/table")
46233

47-
// Verify the adapter implements required interface methods
48-
assert(methodNames.contains("getSnapshot"))
49-
assert(methodNames.contains("versionExists"))
50-
assert(methodNames.contains("getLatestVersion"))
51-
assert(methodNames.contains("close"))
234+
assert(!testUCClient.closeCalled)
235+
adapter.close()
236+
assert(testUCClient.closeCalled)
52237
}
53238
}

0 commit comments

Comments
 (0)