@fabricorg/databricks-testkit 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +179 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -5
- package/dist/index.d.ts +39 -5
- package/dist/index.js +174 -19
- package/dist/index.js.map +1 -1
- package/package.json +12 -14
- package/LICENSE +0 -21
package/dist/index.d.cts
CHANGED
|
@@ -170,7 +170,7 @@ declare function compareToGolden(goldenPath: string, actual: readonly Record<str
|
|
|
170
170
|
* vitest without vi.fn so it is equally usable from cucumber-js worlds.
|
|
171
171
|
*/
|
|
172
172
|
interface RecordedCall {
|
|
173
|
-
method: 'GET' | 'POST';
|
|
173
|
+
method: 'GET' | 'POST' | 'DELETE';
|
|
174
174
|
path: string;
|
|
175
175
|
body?: unknown;
|
|
176
176
|
query?: Record<string, string | number | boolean | undefined>;
|
|
@@ -178,12 +178,13 @@ interface RecordedCall {
|
|
|
178
178
|
interface MockDatabricksClient {
|
|
179
179
|
get<T>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
180
180
|
post<T>(path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>, options?: unknown): Promise<T>;
|
|
181
|
+
request<T>(method: 'GET' | 'POST' | 'DELETE', path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>, options?: unknown): Promise<T>;
|
|
181
182
|
/** Queue the next response for calls whose path starts with `pathPrefix`. FIFO per prefix. */
|
|
182
|
-
queue(method: 'GET' | 'POST', pathPrefix: string, response: unknown): MockDatabricksClient;
|
|
183
|
+
queue(method: 'GET' | 'POST' | 'DELETE', pathPrefix: string, response: unknown): MockDatabricksClient;
|
|
183
184
|
/** Queue an error for the next matching call. */
|
|
184
|
-
queueError(method: 'GET' | 'POST', pathPrefix: string, error: Error): MockDatabricksClient;
|
|
185
|
+
queueError(method: 'GET' | 'POST' | 'DELETE', pathPrefix: string, error: Error): MockDatabricksClient;
|
|
185
186
|
readonly calls: readonly RecordedCall[];
|
|
186
|
-
callsTo(method: 'GET' | 'POST', pathPrefix: string): RecordedCall[];
|
|
187
|
+
callsTo(method: 'GET' | 'POST' | 'DELETE', pathPrefix: string): RecordedCall[];
|
|
187
188
|
}
|
|
188
189
|
declare function createMockDatabricksClient(): MockDatabricksClient;
|
|
189
190
|
|
|
@@ -239,6 +240,8 @@ declare function runLiveSuite(spec: LiveSuiteSpec, runtime?: LiveSuiteRuntime):
|
|
|
239
240
|
declare function renderJUnit(evidence: LiveEvidence): string;
|
|
240
241
|
|
|
241
242
|
declare function sqlRoundTripCheck(): LiveCheck;
|
|
243
|
+
/** Fail closed if governance checks accidentally run with bootstrap or administrator credentials. */
|
|
244
|
+
declare function restrictedPrincipalCheck(): LiveCheck;
|
|
242
245
|
interface DeltaColumnContract {
|
|
243
246
|
name: string;
|
|
244
247
|
type?: string;
|
|
@@ -264,6 +267,37 @@ declare function lakebaseCredentialCheck(options?: LakebaseCheckOptions): LiveCh
|
|
|
264
267
|
/** Exercise the production provider before and after an explicit credential/pool refresh. */
|
|
265
268
|
declare function lakebaseRoundTripCheck(options?: LakebaseCheckOptions): LiveCheck;
|
|
266
269
|
|
|
270
|
+
interface LakebaseControlClient {
|
|
271
|
+
get<T>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
272
|
+
post<T>(path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
273
|
+
request<T>(method: 'DELETE', path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
274
|
+
}
|
|
275
|
+
interface EphemeralLakebaseBranch {
|
|
276
|
+
project: string;
|
|
277
|
+
branch: string;
|
|
278
|
+
branchId: string;
|
|
279
|
+
endpoint: string;
|
|
280
|
+
host: string;
|
|
281
|
+
databaseResource: string;
|
|
282
|
+
database: string;
|
|
283
|
+
createdAt: string;
|
|
284
|
+
expiresAt: string;
|
|
285
|
+
}
|
|
286
|
+
interface LakebaseBranchOptions {
|
|
287
|
+
env?: Record<string, string | undefined>;
|
|
288
|
+
client?: LakebaseControlClient;
|
|
289
|
+
project?: string;
|
|
290
|
+
branchId: string;
|
|
291
|
+
ttlMs: number;
|
|
292
|
+
pollIntervalMs?: number;
|
|
293
|
+
timeoutMs?: number;
|
|
294
|
+
now?: () => Date;
|
|
295
|
+
}
|
|
296
|
+
declare function resolveLakebaseProject(env: Record<string, string | undefined>, explicit?: string): string;
|
|
297
|
+
declare function toLakebaseBranchId(environmentName: string): string;
|
|
298
|
+
declare function createEphemeralLakebaseBranch(options: LakebaseBranchOptions): Promise<EphemeralLakebaseBranch>;
|
|
299
|
+
declare function deleteEphemeralLakebaseBranch(branch: Pick<EphemeralLakebaseBranch, 'branch'>, options?: Omit<LakebaseBranchOptions, 'branchId' | 'ttlMs' | 'project'>): Promise<void>;
|
|
300
|
+
|
|
267
301
|
/** Assert secret key metadata only; values are never requested. */
|
|
268
302
|
declare function secretScopeCheck(): LiveCheck;
|
|
269
303
|
interface AppHealthCheckOptions {
|
|
@@ -298,4 +332,4 @@ interface PollOptions {
|
|
|
298
332
|
}
|
|
299
333
|
declare function waitForPipelineUpdate(client: WorkspaceClientLike, pipelineId: string, updateId: string, options?: PollOptions): Promise<Record<string, unknown>>;
|
|
300
334
|
|
|
301
|
-
export { type AppHealthCheckOptions, type AutoLoaderCheckOptions, type CreateContextOptions, type DefinedLiveSuite, type DeltaColumnContract, type DuckDbConnectionLike, type DuckDbContextOptions, type ExecutionContext, type LakebaseCheckOptions, type LiveCheck, type LiveCheckContext, type LiveCheckResult, type LiveCheckStatus, type LiveEvidence, type LiveSuiteRuntime, type LiveSuiteSpec, type MockDatabricksClient, type PollOptions, type RecordedCall, TableFixture, type TestProfile, type VolumeIOCheckOptions, type WorkspaceClientLike, type WorkspaceContextOptions, appHealthCheck, assertDeltaContract, autoLoaderIngestionCheck, compareToGolden, createClientFromEnv, createDuckDbContext, createExecutionContext, createLakebaseTestProvider, createMockDatabricksClient, createWorkspaceContext, customLiveCheck, dbtBuildCheck, defineLiveSuite, deleteVolumeFile, deltaContractCheck, ensureVolumeDirectory, expectQueryToMatchGolden, expectTable, jobRunCheck, lakebaseCredentialCheck, lakebaseRoundTripCheck, normalizeRow, normalizeVolumeRoot, notebookSubmitCheck, parseStatementRows, pipelineRefreshCheck, renderJUnit, resolveProfile, resolveTokenProvider, runLiveSuite, secretScopeCheck, sqlRoundTripCheck, toNamedParameters, unityCatalogAccessCheck, uploadVolumeFile, volumeIOCheck, waitForPipelineUpdate };
|
|
335
|
+
export { type AppHealthCheckOptions, type AutoLoaderCheckOptions, type CreateContextOptions, type DefinedLiveSuite, type DeltaColumnContract, type DuckDbConnectionLike, type DuckDbContextOptions, type EphemeralLakebaseBranch, type ExecutionContext, type LakebaseBranchOptions, type LakebaseCheckOptions, type LakebaseControlClient, type LiveCheck, type LiveCheckContext, type LiveCheckResult, type LiveCheckStatus, type LiveEvidence, type LiveSuiteRuntime, type LiveSuiteSpec, type MockDatabricksClient, type PollOptions, type RecordedCall, TableFixture, type TestProfile, type VolumeIOCheckOptions, type WorkspaceClientLike, type WorkspaceContextOptions, appHealthCheck, assertDeltaContract, autoLoaderIngestionCheck, compareToGolden, createClientFromEnv, createDuckDbContext, createEphemeralLakebaseBranch, createExecutionContext, createLakebaseTestProvider, createMockDatabricksClient, createWorkspaceContext, customLiveCheck, dbtBuildCheck, defineLiveSuite, deleteEphemeralLakebaseBranch, deleteVolumeFile, deltaContractCheck, ensureVolumeDirectory, expectQueryToMatchGolden, expectTable, jobRunCheck, lakebaseCredentialCheck, lakebaseRoundTripCheck, normalizeRow, normalizeVolumeRoot, notebookSubmitCheck, parseStatementRows, pipelineRefreshCheck, renderJUnit, resolveLakebaseProject, resolveProfile, resolveTokenProvider, restrictedPrincipalCheck, runLiveSuite, secretScopeCheck, sqlRoundTripCheck, toLakebaseBranchId, toNamedParameters, unityCatalogAccessCheck, uploadVolumeFile, volumeIOCheck, waitForPipelineUpdate };
|
package/dist/index.d.ts
CHANGED
|
@@ -170,7 +170,7 @@ declare function compareToGolden(goldenPath: string, actual: readonly Record<str
|
|
|
170
170
|
* vitest without vi.fn so it is equally usable from cucumber-js worlds.
|
|
171
171
|
*/
|
|
172
172
|
interface RecordedCall {
|
|
173
|
-
method: 'GET' | 'POST';
|
|
173
|
+
method: 'GET' | 'POST' | 'DELETE';
|
|
174
174
|
path: string;
|
|
175
175
|
body?: unknown;
|
|
176
176
|
query?: Record<string, string | number | boolean | undefined>;
|
|
@@ -178,12 +178,13 @@ interface RecordedCall {
|
|
|
178
178
|
interface MockDatabricksClient {
|
|
179
179
|
get<T>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
180
180
|
post<T>(path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>, options?: unknown): Promise<T>;
|
|
181
|
+
request<T>(method: 'GET' | 'POST' | 'DELETE', path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>, options?: unknown): Promise<T>;
|
|
181
182
|
/** Queue the next response for calls whose path starts with `pathPrefix`. FIFO per prefix. */
|
|
182
|
-
queue(method: 'GET' | 'POST', pathPrefix: string, response: unknown): MockDatabricksClient;
|
|
183
|
+
queue(method: 'GET' | 'POST' | 'DELETE', pathPrefix: string, response: unknown): MockDatabricksClient;
|
|
183
184
|
/** Queue an error for the next matching call. */
|
|
184
|
-
queueError(method: 'GET' | 'POST', pathPrefix: string, error: Error): MockDatabricksClient;
|
|
185
|
+
queueError(method: 'GET' | 'POST' | 'DELETE', pathPrefix: string, error: Error): MockDatabricksClient;
|
|
185
186
|
readonly calls: readonly RecordedCall[];
|
|
186
|
-
callsTo(method: 'GET' | 'POST', pathPrefix: string): RecordedCall[];
|
|
187
|
+
callsTo(method: 'GET' | 'POST' | 'DELETE', pathPrefix: string): RecordedCall[];
|
|
187
188
|
}
|
|
188
189
|
declare function createMockDatabricksClient(): MockDatabricksClient;
|
|
189
190
|
|
|
@@ -239,6 +240,8 @@ declare function runLiveSuite(spec: LiveSuiteSpec, runtime?: LiveSuiteRuntime):
|
|
|
239
240
|
declare function renderJUnit(evidence: LiveEvidence): string;
|
|
240
241
|
|
|
241
242
|
declare function sqlRoundTripCheck(): LiveCheck;
|
|
243
|
+
/** Fail closed if governance checks accidentally run with bootstrap or administrator credentials. */
|
|
244
|
+
declare function restrictedPrincipalCheck(): LiveCheck;
|
|
242
245
|
interface DeltaColumnContract {
|
|
243
246
|
name: string;
|
|
244
247
|
type?: string;
|
|
@@ -264,6 +267,37 @@ declare function lakebaseCredentialCheck(options?: LakebaseCheckOptions): LiveCh
|
|
|
264
267
|
/** Exercise the production provider before and after an explicit credential/pool refresh. */
|
|
265
268
|
declare function lakebaseRoundTripCheck(options?: LakebaseCheckOptions): LiveCheck;
|
|
266
269
|
|
|
270
|
+
interface LakebaseControlClient {
|
|
271
|
+
get<T>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
272
|
+
post<T>(path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
273
|
+
request<T>(method: 'DELETE', path: string, body?: unknown, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
274
|
+
}
|
|
275
|
+
interface EphemeralLakebaseBranch {
|
|
276
|
+
project: string;
|
|
277
|
+
branch: string;
|
|
278
|
+
branchId: string;
|
|
279
|
+
endpoint: string;
|
|
280
|
+
host: string;
|
|
281
|
+
databaseResource: string;
|
|
282
|
+
database: string;
|
|
283
|
+
createdAt: string;
|
|
284
|
+
expiresAt: string;
|
|
285
|
+
}
|
|
286
|
+
interface LakebaseBranchOptions {
|
|
287
|
+
env?: Record<string, string | undefined>;
|
|
288
|
+
client?: LakebaseControlClient;
|
|
289
|
+
project?: string;
|
|
290
|
+
branchId: string;
|
|
291
|
+
ttlMs: number;
|
|
292
|
+
pollIntervalMs?: number;
|
|
293
|
+
timeoutMs?: number;
|
|
294
|
+
now?: () => Date;
|
|
295
|
+
}
|
|
296
|
+
declare function resolveLakebaseProject(env: Record<string, string | undefined>, explicit?: string): string;
|
|
297
|
+
declare function toLakebaseBranchId(environmentName: string): string;
|
|
298
|
+
declare function createEphemeralLakebaseBranch(options: LakebaseBranchOptions): Promise<EphemeralLakebaseBranch>;
|
|
299
|
+
declare function deleteEphemeralLakebaseBranch(branch: Pick<EphemeralLakebaseBranch, 'branch'>, options?: Omit<LakebaseBranchOptions, 'branchId' | 'ttlMs' | 'project'>): Promise<void>;
|
|
300
|
+
|
|
267
301
|
/** Assert secret key metadata only; values are never requested. */
|
|
268
302
|
declare function secretScopeCheck(): LiveCheck;
|
|
269
303
|
interface AppHealthCheckOptions {
|
|
@@ -298,4 +332,4 @@ interface PollOptions {
|
|
|
298
332
|
}
|
|
299
333
|
declare function waitForPipelineUpdate(client: WorkspaceClientLike, pipelineId: string, updateId: string, options?: PollOptions): Promise<Record<string, unknown>>;
|
|
300
334
|
|
|
301
|
-
export { type AppHealthCheckOptions, type AutoLoaderCheckOptions, type CreateContextOptions, type DefinedLiveSuite, type DeltaColumnContract, type DuckDbConnectionLike, type DuckDbContextOptions, type ExecutionContext, type LakebaseCheckOptions, type LiveCheck, type LiveCheckContext, type LiveCheckResult, type LiveCheckStatus, type LiveEvidence, type LiveSuiteRuntime, type LiveSuiteSpec, type MockDatabricksClient, type PollOptions, type RecordedCall, TableFixture, type TestProfile, type VolumeIOCheckOptions, type WorkspaceClientLike, type WorkspaceContextOptions, appHealthCheck, assertDeltaContract, autoLoaderIngestionCheck, compareToGolden, createClientFromEnv, createDuckDbContext, createExecutionContext, createLakebaseTestProvider, createMockDatabricksClient, createWorkspaceContext, customLiveCheck, dbtBuildCheck, defineLiveSuite, deleteVolumeFile, deltaContractCheck, ensureVolumeDirectory, expectQueryToMatchGolden, expectTable, jobRunCheck, lakebaseCredentialCheck, lakebaseRoundTripCheck, normalizeRow, normalizeVolumeRoot, notebookSubmitCheck, parseStatementRows, pipelineRefreshCheck, renderJUnit, resolveProfile, resolveTokenProvider, runLiveSuite, secretScopeCheck, sqlRoundTripCheck, toNamedParameters, unityCatalogAccessCheck, uploadVolumeFile, volumeIOCheck, waitForPipelineUpdate };
|
|
335
|
+
export { type AppHealthCheckOptions, type AutoLoaderCheckOptions, type CreateContextOptions, type DefinedLiveSuite, type DeltaColumnContract, type DuckDbConnectionLike, type DuckDbContextOptions, type EphemeralLakebaseBranch, type ExecutionContext, type LakebaseBranchOptions, type LakebaseCheckOptions, type LakebaseControlClient, type LiveCheck, type LiveCheckContext, type LiveCheckResult, type LiveCheckStatus, type LiveEvidence, type LiveSuiteRuntime, type LiveSuiteSpec, type MockDatabricksClient, type PollOptions, type RecordedCall, TableFixture, type TestProfile, type VolumeIOCheckOptions, type WorkspaceClientLike, type WorkspaceContextOptions, appHealthCheck, assertDeltaContract, autoLoaderIngestionCheck, compareToGolden, createClientFromEnv, createDuckDbContext, createEphemeralLakebaseBranch, createExecutionContext, createLakebaseTestProvider, createMockDatabricksClient, createWorkspaceContext, customLiveCheck, dbtBuildCheck, defineLiveSuite, deleteEphemeralLakebaseBranch, deleteVolumeFile, deltaContractCheck, ensureVolumeDirectory, expectQueryToMatchGolden, expectTable, jobRunCheck, lakebaseCredentialCheck, lakebaseRoundTripCheck, normalizeRow, normalizeVolumeRoot, notebookSubmitCheck, parseStatementRows, pipelineRefreshCheck, renderJUnit, resolveLakebaseProject, resolveProfile, resolveTokenProvider, restrictedPrincipalCheck, runLiveSuite, secretScopeCheck, sqlRoundTripCheck, toLakebaseBranchId, toNamedParameters, unityCatalogAccessCheck, uploadVolumeFile, volumeIOCheck, waitForPipelineUpdate };
|
package/dist/index.js
CHANGED
|
@@ -91,7 +91,7 @@ function looselyEqual(a, b) {
|
|
|
91
91
|
|
|
92
92
|
// src/mock-client.ts
|
|
93
93
|
function createMockDatabricksClient() {
|
|
94
|
-
const queues = { GET: [], POST: [] };
|
|
94
|
+
const queues = { GET: [], POST: [], DELETE: [] };
|
|
95
95
|
const calls = [];
|
|
96
96
|
function take(method, path) {
|
|
97
97
|
const index = queues[method].findIndex((q) => path.startsWith(q.pathPrefix));
|
|
@@ -115,6 +115,12 @@ function createMockDatabricksClient() {
|
|
|
115
115
|
if (queued.error) throw queued.error;
|
|
116
116
|
return queued.response;
|
|
117
117
|
},
|
|
118
|
+
async request(method, path, body, query) {
|
|
119
|
+
calls.push({ method, path, body, query });
|
|
120
|
+
const queued = take(method, path);
|
|
121
|
+
if (queued.error) throw queued.error;
|
|
122
|
+
return queued.response;
|
|
123
|
+
},
|
|
118
124
|
queue(method, pathPrefix, response) {
|
|
119
125
|
queues[method].push({ pathPrefix, response });
|
|
120
126
|
return client;
|
|
@@ -280,6 +286,26 @@ function sqlRoundTripCheck() {
|
|
|
280
286
|
}
|
|
281
287
|
};
|
|
282
288
|
}
|
|
289
|
+
function restrictedPrincipalCheck() {
|
|
290
|
+
return {
|
|
291
|
+
id: "restricted-principal",
|
|
292
|
+
description: "Live checks use the designated least-privilege service principal",
|
|
293
|
+
configured: (env) => Boolean(env.DBX_TEST_RESTRICTED_PRINCIPAL_ID),
|
|
294
|
+
async run({ env }) {
|
|
295
|
+
const expected = env.DBX_TEST_RESTRICTED_PRINCIPAL_ID?.toLowerCase();
|
|
296
|
+
const actual = env.DATABRICKS_CLIENT_ID?.toLowerCase();
|
|
297
|
+
if (!expected || !actual) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
"Restricted-principal check requires DBX_TEST_RESTRICTED_PRINCIPAL_ID and DATABRICKS_CLIENT_ID"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (actual !== expected) {
|
|
303
|
+
throw new Error(`Live suite is using principal '${actual}', expected '${expected}'`);
|
|
304
|
+
}
|
|
305
|
+
return { principalId: actual };
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
283
309
|
function assertDeltaContract(table, rows, columns) {
|
|
284
310
|
for (const expected of columns) {
|
|
285
311
|
const actual = rows.find((row) => String(row.col_name ?? row.column_name) === expected.name);
|
|
@@ -371,17 +397,21 @@ function notebookSubmitCheck() {
|
|
|
371
397
|
return {
|
|
372
398
|
id: "notebook",
|
|
373
399
|
description: "Databricks notebook submit run reaches SUCCESS",
|
|
374
|
-
configured: (env) => Boolean(
|
|
400
|
+
configured: (env) => Boolean(
|
|
401
|
+
env.DBX_TEST_NOTEBOOK_PATH && (env.DBX_TEST_CLUSTER_ID || env.DBX_TEST_NOTEBOOK_SERVERLESS === "1")
|
|
402
|
+
),
|
|
375
403
|
async run({ env, client }) {
|
|
376
|
-
if (!env.DBX_TEST_NOTEBOOK_PATH || !env.DBX_TEST_CLUSTER_ID) {
|
|
377
|
-
throw new Error(
|
|
404
|
+
if (!env.DBX_TEST_NOTEBOOK_PATH || !env.DBX_TEST_CLUSTER_ID && env.DBX_TEST_NOTEBOOK_SERVERLESS !== "1") {
|
|
405
|
+
throw new Error(
|
|
406
|
+
"Notebook submit requires DBX_TEST_NOTEBOOK_PATH and either DBX_TEST_CLUSTER_ID or DBX_TEST_NOTEBOOK_SERVERLESS=1"
|
|
407
|
+
);
|
|
378
408
|
}
|
|
379
409
|
const submitted = await client.post("/api/2.1/jobs/runs/submit", {
|
|
380
410
|
run_name: `fabric-experiments-notebook-${Date.now()}`,
|
|
381
411
|
tasks: [
|
|
382
412
|
{
|
|
383
413
|
task_key: "notebook",
|
|
384
|
-
existing_cluster_id: env.DBX_TEST_CLUSTER_ID,
|
|
414
|
+
...env.DBX_TEST_CLUSTER_ID ? { existing_cluster_id: env.DBX_TEST_CLUSTER_ID } : {},
|
|
385
415
|
notebook_task: { notebook_path: env.DBX_TEST_NOTEBOOK_PATH }
|
|
386
416
|
}
|
|
387
417
|
]
|
|
@@ -401,29 +431,59 @@ function dbtBuildCheck() {
|
|
|
401
431
|
return {
|
|
402
432
|
id: "dbt",
|
|
403
433
|
description: "Databricks dbt task reaches SUCCESS",
|
|
404
|
-
configured: (env) => Boolean(
|
|
434
|
+
configured: (env) => Boolean(
|
|
435
|
+
(env.DBX_TEST_DBT_GIT_URL || env.DBX_TEST_DBT_PROJECT_DIR) && env.DATABRICKS_WAREHOUSE_ID
|
|
436
|
+
),
|
|
405
437
|
async run({ env, client }) {
|
|
406
|
-
if (!env.DBX_TEST_DBT_GIT_URL || !env.DATABRICKS_WAREHOUSE_ID) {
|
|
407
|
-
throw new Error(
|
|
438
|
+
if (!env.DBX_TEST_DBT_GIT_URL && !env.DBX_TEST_DBT_PROJECT_DIR || !env.DATABRICKS_WAREHOUSE_ID) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
"dbt build requires DBX_TEST_DBT_GIT_URL or DBX_TEST_DBT_PROJECT_DIR, plus DATABRICKS_WAREHOUSE_ID"
|
|
441
|
+
);
|
|
408
442
|
}
|
|
409
|
-
const
|
|
443
|
+
const sourceVars = JSON.stringify({
|
|
444
|
+
source_catalog: env.DBX_TEST_DBT_SOURCE_CATALOG ?? env.DBX_TEST_CATALOG,
|
|
445
|
+
source_schema: env.DBX_TEST_DBT_SOURCE_SCHEMA
|
|
446
|
+
});
|
|
447
|
+
const serverless = env.DBX_TEST_DBT_SERVERLESS === "1";
|
|
448
|
+
const request = {
|
|
410
449
|
run_name: `fabric-experiments-dbt-${Date.now()}`,
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
450
|
+
...env.DBX_TEST_DBT_GIT_URL ? {
|
|
451
|
+
git_source: {
|
|
452
|
+
git_url: env.DBX_TEST_DBT_GIT_URL,
|
|
453
|
+
git_provider: env.DBX_TEST_DBT_GIT_PROVIDER ?? "gitHub",
|
|
454
|
+
git_branch: env.DBX_TEST_DBT_GIT_BRANCH ?? "main"
|
|
455
|
+
}
|
|
456
|
+
} : {},
|
|
416
457
|
tasks: [
|
|
417
458
|
{
|
|
418
459
|
task_key: "dbt_build",
|
|
460
|
+
...serverless ? { environment_key: "dbt" } : {},
|
|
419
461
|
dbt_task: {
|
|
420
|
-
commands: ["dbt deps",
|
|
462
|
+
commands: ["dbt deps", `dbt build --vars '${sourceVars}'`],
|
|
421
463
|
warehouse_id: env.DATABRICKS_WAREHOUSE_ID,
|
|
464
|
+
...env.DBX_TEST_CATALOG ? { catalog: env.DBX_TEST_CATALOG } : {},
|
|
465
|
+
...env.DBX_TEST_SCHEMA ? { schema: env.DBX_TEST_SCHEMA } : {},
|
|
466
|
+
...env.DBX_TEST_DBT_GIT_URL ? {} : { source: "WORKSPACE" },
|
|
422
467
|
...env.DBX_TEST_DBT_PROJECT_DIR ? { project_directory: env.DBX_TEST_DBT_PROJECT_DIR } : {}
|
|
423
468
|
}
|
|
424
469
|
}
|
|
425
|
-
]
|
|
426
|
-
|
|
470
|
+
],
|
|
471
|
+
...serverless ? {
|
|
472
|
+
environments: [
|
|
473
|
+
{
|
|
474
|
+
environment_key: "dbt",
|
|
475
|
+
spec: {
|
|
476
|
+
environment_version: "2",
|
|
477
|
+
dependencies: ["dbt-databricks>=1.8,<2"]
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
} : {}
|
|
482
|
+
};
|
|
483
|
+
const submitted = await client.post(
|
|
484
|
+
"/api/2.1/jobs/runs/submit",
|
|
485
|
+
request
|
|
486
|
+
);
|
|
427
487
|
if (!submitted.run_id) throw new Error("dbt submit returned no run_id");
|
|
428
488
|
const run = await waitForDatabricksRun(client, submitted.run_id, {
|
|
429
489
|
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
@@ -431,7 +491,10 @@ function dbtBuildCheck() {
|
|
|
431
491
|
});
|
|
432
492
|
const result = run.state?.result_state;
|
|
433
493
|
if (result !== "SUCCESS") throw new Error(`dbt run ended in ${result ?? "UNKNOWN"}`);
|
|
434
|
-
return {
|
|
494
|
+
return {
|
|
495
|
+
runId: submitted.run_id,
|
|
496
|
+
source: env.DBX_TEST_DBT_GIT_URL ?? env.DBX_TEST_DBT_PROJECT_DIR
|
|
497
|
+
};
|
|
435
498
|
}
|
|
436
499
|
};
|
|
437
500
|
}
|
|
@@ -507,6 +570,98 @@ function lakebaseRoundTripCheck(options = {}) {
|
|
|
507
570
|
};
|
|
508
571
|
}
|
|
509
572
|
|
|
573
|
+
// src/lakebase-branch.ts
|
|
574
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
575
|
+
var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
576
|
+
function resolveLakebaseProject(env, explicit) {
|
|
577
|
+
const project = explicit ?? env.DBX_TEST_LAKEBASE_PROJECT;
|
|
578
|
+
if (!project || !/^projects\/[a-z0-9][a-z0-9-]{0,62}$/.test(project)) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
`Lakebase project must look like projects/fabric-experiments; got '${project ?? ""}'`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
return project;
|
|
584
|
+
}
|
|
585
|
+
function toLakebaseBranchId(environmentName) {
|
|
586
|
+
const normalized = environmentName.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
587
|
+
const prefixed = normalized.startsWith("fx-") ? normalized : `fx-${normalized}`;
|
|
588
|
+
const trimmed = prefixed.slice(0, 63).replace(/-+$/g, "");
|
|
589
|
+
if (!/^fx-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(trimmed)) {
|
|
590
|
+
throw new Error(`Cannot derive a safe Lakebase branch ID from '${environmentName}'`);
|
|
591
|
+
}
|
|
592
|
+
return trimmed;
|
|
593
|
+
}
|
|
594
|
+
async function createEphemeralLakebaseBranch(options) {
|
|
595
|
+
const env = options.env ?? process.env;
|
|
596
|
+
const client = options.client ?? createClientFromEnv(env);
|
|
597
|
+
const project = resolveLakebaseProject(env, options.project);
|
|
598
|
+
const branchId = toLakebaseBranchId(options.branchId);
|
|
599
|
+
const ttlSeconds = Math.max(60, Math.ceil(options.ttlMs / 1e3));
|
|
600
|
+
const operation = await client.post(
|
|
601
|
+
`/api/2.0/postgres/${project}/branches`,
|
|
602
|
+
{ spec: { ttl: `${ttlSeconds}s` } },
|
|
603
|
+
{ branch_id: branchId, replace_existing: false }
|
|
604
|
+
);
|
|
605
|
+
const branch = await waitForOperation(client, operation, options);
|
|
606
|
+
const branchName = branch?.name ?? `${project}/branches/${branchId}`;
|
|
607
|
+
const [endpointResult, databaseResult] = await Promise.all([
|
|
608
|
+
client.get(`/api/2.0/postgres/${branchName}/endpoints`),
|
|
609
|
+
client.get(`/api/2.0/postgres/${branchName}/databases`)
|
|
610
|
+
]);
|
|
611
|
+
const endpoint = endpointResult.endpoints?.find((item) => item.endpoint_id === "primary") ?? endpointResult.endpoints?.[0];
|
|
612
|
+
const database = databaseResult.databases?.[0];
|
|
613
|
+
const host = endpoint?.status?.hosts?.host;
|
|
614
|
+
const postgresDatabase = database?.status?.postgres_database;
|
|
615
|
+
if (!endpoint?.name || !host || !database?.name || !postgresDatabase) {
|
|
616
|
+
throw new Error(`Lakebase branch '${branchName}' did not expose an endpoint and database`);
|
|
617
|
+
}
|
|
618
|
+
const createdAt = branch?.create_time ?? (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
619
|
+
return {
|
|
620
|
+
project,
|
|
621
|
+
branch: branchName,
|
|
622
|
+
branchId,
|
|
623
|
+
endpoint: endpoint.name,
|
|
624
|
+
host,
|
|
625
|
+
databaseResource: database.name,
|
|
626
|
+
database: postgresDatabase,
|
|
627
|
+
createdAt,
|
|
628
|
+
expiresAt: new Date(Date.parse(createdAt) + ttlSeconds * 1e3).toISOString()
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async function deleteEphemeralLakebaseBranch(branch, options = {}) {
|
|
632
|
+
if (!/^projects\/[a-z0-9-]+\/branches\/fx-[a-z0-9-]+$/.test(branch.branch)) {
|
|
633
|
+
throw new Error(`Refusing to delete non-ephemeral Lakebase branch '${branch.branch}'`);
|
|
634
|
+
}
|
|
635
|
+
const env = options.env ?? process.env;
|
|
636
|
+
const client = options.client ?? createClientFromEnv(env);
|
|
637
|
+
const operation = await client.request(
|
|
638
|
+
"DELETE",
|
|
639
|
+
`/api/2.0/postgres/${branch.branch}`,
|
|
640
|
+
void 0,
|
|
641
|
+
{ purge: true }
|
|
642
|
+
);
|
|
643
|
+
await waitForOperation(client, operation, options);
|
|
644
|
+
}
|
|
645
|
+
async function waitForOperation(client, initial, options) {
|
|
646
|
+
let operation = initial;
|
|
647
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
648
|
+
while (!operation.done) {
|
|
649
|
+
if (!operation.name) throw new Error("Lakebase operation did not return an operation name");
|
|
650
|
+
if (Date.now() >= deadline) throw new Error(`Lakebase operation '${operation.name}' timed out`);
|
|
651
|
+
await delay(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
652
|
+
operation = await client.get(`/api/2.0/postgres/${operation.name}`);
|
|
653
|
+
}
|
|
654
|
+
if (operation.error) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
`Lakebase operation failed${operation.error.code ? ` (${operation.error.code})` : ""}: ${operation.error.message ?? "unknown error"}`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return operation.response;
|
|
660
|
+
}
|
|
661
|
+
function delay(ms) {
|
|
662
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
663
|
+
}
|
|
664
|
+
|
|
510
665
|
// src/databricks-http.ts
|
|
511
666
|
async function databricksFetch(env, pathOrUrl, init = {}, fetchImpl = fetch) {
|
|
512
667
|
const host = env.DATABRICKS_HOST?.replace(/\/$/, "");
|
|
@@ -685,6 +840,6 @@ function autoLoaderIngestionCheck(options = {}) {
|
|
|
685
840
|
};
|
|
686
841
|
}
|
|
687
842
|
|
|
688
|
-
export { appHealthCheck, assertDeltaContract, autoLoaderIngestionCheck, createExecutionContext, createLakebaseTestProvider, createMockDatabricksClient, customLiveCheck, dbtBuildCheck, defineLiveSuite, deleteVolumeFile, deltaContractCheck, ensureVolumeDirectory, expectQueryToMatchGolden, expectTable, jobRunCheck, lakebaseCredentialCheck, lakebaseRoundTripCheck, normalizeVolumeRoot, notebookSubmitCheck, pipelineRefreshCheck, renderJUnit, resolveProfile, runLiveSuite, secretScopeCheck, sqlRoundTripCheck, unityCatalogAccessCheck, uploadVolumeFile, volumeIOCheck, waitForPipelineUpdate };
|
|
843
|
+
export { appHealthCheck, assertDeltaContract, autoLoaderIngestionCheck, createEphemeralLakebaseBranch, createExecutionContext, createLakebaseTestProvider, createMockDatabricksClient, customLiveCheck, dbtBuildCheck, defineLiveSuite, deleteEphemeralLakebaseBranch, deleteVolumeFile, deltaContractCheck, ensureVolumeDirectory, expectQueryToMatchGolden, expectTable, jobRunCheck, lakebaseCredentialCheck, lakebaseRoundTripCheck, normalizeVolumeRoot, notebookSubmitCheck, pipelineRefreshCheck, renderJUnit, resolveLakebaseProject, resolveProfile, restrictedPrincipalCheck, runLiveSuite, secretScopeCheck, sqlRoundTripCheck, toLakebaseBranchId, unityCatalogAccessCheck, uploadVolumeFile, volumeIOCheck, waitForPipelineUpdate };
|
|
689
844
|
//# sourceMappingURL=index.js.map
|
|
690
845
|
//# sourceMappingURL=index.js.map
|