|
16 | 16 |
|
17 | 17 | package io.delta.kernel.spark.catalog |
18 | 18 |
|
| 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 |
19 | 27 | import org.scalatest.funsuite.AnyFunSuite |
20 | 28 |
|
21 | 29 | /** |
22 | | - * Basic validation tests for [[UCManagedCommitClientAdapter]]. |
| 30 | + * Tests for [[UCManagedCommitClientAdapter]]. |
23 | 31 | * |
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. |
27 | 34 | */ |
28 | 35 | class UCManagedCommitClientAdapterSuite extends AnyFunSuite { |
29 | 36 |
|
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() |
33 | 44 |
|
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" |
37 | 46 |
|
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")) |
41 | 224 | } |
42 | 225 |
|
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") |
46 | 233 |
|
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) |
52 | 237 | } |
53 | 238 | } |
0 commit comments