@fabricorg/databricks-testkit 0.2.2 → 0.2.4
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/{chunk-FBADA7LQ.js → chunk-SCVVMUAF.js} +50 -3
- package/dist/chunk-SCVVMUAF.js.map +1 -0
- package/dist/index.cjs +75 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +30 -5
- package/dist/index.js.map +1 -1
- package/dist/parity.cjs +48 -1
- package/dist/parity.cjs.map +1 -1
- package/dist/parity.js +1 -1
- package/dist/{workspace-context-AIUYISUS.js → workspace-context-VLBKMX3V.js} +3 -3
- package/dist/{workspace-context-AIUYISUS.js.map → workspace-context-VLBKMX3V.js.map} +1 -1
- package/package.json +2 -2
- package/dist/chunk-FBADA7LQ.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseFixtureColumns, resolveFixtureRows } from './chunk-F3KU6VZS.js';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
2
3
|
import { DatabricksRestClient, waitForDatabricksStatement } from '@fabric-harness/databricks';
|
|
3
4
|
|
|
4
5
|
async function createWorkspaceContext(options = {}) {
|
|
@@ -156,6 +157,39 @@ function createClientFromEnv(env, fetchImpl) {
|
|
|
156
157
|
}
|
|
157
158
|
function resolveTokenProvider(host, env, fetchImpl = fetch) {
|
|
158
159
|
if (env.DATABRICKS_BEARER) return env.DATABRICKS_BEARER;
|
|
160
|
+
const authType = env.DATABRICKS_AUTH_TYPE;
|
|
161
|
+
if (authType === "env-oidc" || authType === "file-oidc") {
|
|
162
|
+
let cached = null;
|
|
163
|
+
return async () => {
|
|
164
|
+
if (cached && Date.now() < cached.expiresAt) return cached.token;
|
|
165
|
+
const subjectToken = await resolveFederatedSubjectToken(authType, env);
|
|
166
|
+
const params = new URLSearchParams({
|
|
167
|
+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
168
|
+
subject_token: subjectToken,
|
|
169
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
|
|
170
|
+
scope: env.DATABRICKS_SCOPE ?? "all-apis"
|
|
171
|
+
});
|
|
172
|
+
if (env.DATABRICKS_CLIENT_ID) params.set("client_id", env.DATABRICKS_CLIENT_ID);
|
|
173
|
+
const response = await fetchImpl(`${host.replace(/\/$/, "")}/oidc/v1/token`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
176
|
+
body: params.toString()
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error(`OAuth federated token exchange failed: ${response.status}`);
|
|
180
|
+
}
|
|
181
|
+
const body = await response.json();
|
|
182
|
+
if (typeof body.access_token !== "string" || !body.access_token) {
|
|
183
|
+
throw new Error("OAuth federated token exchange returned no access_token");
|
|
184
|
+
}
|
|
185
|
+
const expiresIn = typeof body.expires_in === "number" ? body.expires_in : 3600;
|
|
186
|
+
cached = {
|
|
187
|
+
token: body.access_token,
|
|
188
|
+
expiresAt: Date.now() + Math.max(1, expiresIn - 300) * 1e3
|
|
189
|
+
};
|
|
190
|
+
return body.access_token;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
159
193
|
const clientId = env.DATABRICKS_CLIENT_ID;
|
|
160
194
|
const clientSecret = env.DATABRICKS_CLIENT_SECRET;
|
|
161
195
|
if (clientId && clientSecret) {
|
|
@@ -186,10 +220,23 @@ function resolveTokenProvider(host, env, fetchImpl = fetch) {
|
|
|
186
220
|
}
|
|
187
221
|
if (env.DATABRICKS_TOKEN) return env.DATABRICKS_TOKEN;
|
|
188
222
|
throw new Error(
|
|
189
|
-
"Live profile requires DATABRICKS_BEARER, DATABRICKS_CLIENT_ID/SECRET, or DATABRICKS_TOKEN"
|
|
223
|
+
"Live profile requires DATABRICKS_BEARER, env/file OIDC federation, DATABRICKS_CLIENT_ID/SECRET, or DATABRICKS_TOKEN"
|
|
190
224
|
);
|
|
191
225
|
}
|
|
226
|
+
async function resolveFederatedSubjectToken(authType, env) {
|
|
227
|
+
if (authType === "env-oidc") {
|
|
228
|
+
const tokenEnv = env.DATABRICKS_OIDC_TOKEN_ENV ?? "DATABRICKS_OIDC_TOKEN";
|
|
229
|
+
const token2 = env[tokenEnv]?.trim();
|
|
230
|
+
if (!token2) throw new Error(`Federated OIDC token environment variable ${tokenEnv} is empty`);
|
|
231
|
+
return token2;
|
|
232
|
+
}
|
|
233
|
+
const path = env.DATABRICKS_OIDC_TOKEN_FILEPATH;
|
|
234
|
+
if (!path) throw new Error("DATABRICKS_OIDC_TOKEN_FILEPATH is required for file-oidc");
|
|
235
|
+
const token = (await readFile(path, "utf8")).trim();
|
|
236
|
+
if (!token) throw new Error(`Federated OIDC token file ${path} is empty`);
|
|
237
|
+
return token;
|
|
238
|
+
}
|
|
192
239
|
|
|
193
240
|
export { createClientFromEnv, createWorkspaceContext, parseStatementRows, resolveTokenProvider, toNamedParameters };
|
|
194
|
-
//# sourceMappingURL=chunk-
|
|
195
|
-
//# sourceMappingURL=chunk-
|
|
241
|
+
//# sourceMappingURL=chunk-SCVVMUAF.js.map
|
|
242
|
+
//# sourceMappingURL=chunk-SCVVMUAF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workspace-context.ts"],"names":["token"],"mappings":"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","file":"chunk-SCVVMUAF.js","sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { DatabricksRestClient, waitForDatabricksStatement } from '@fabric-harness/databricks';\nimport type { ExecutionContext } from './context.js';\nimport { type TableFixture, parseFixtureColumns, resolveFixtureRows } from './fixtures.js';\n\n/** Structural slice of DatabricksRestClient the workspace context uses (injectable in tests). */\nexport interface WorkspaceClientLike {\n get<T>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;\n post<T>(\n path: string,\n body?: unknown,\n query?: Record<string, string | number | boolean | undefined>,\n options?: unknown,\n ): Promise<T>;\n}\n\nexport interface WorkspaceContextOptions {\n env?: Record<string, string | undefined>;\n /** Schema within the catalog used by qual(); defaults to DBX_TEST_SCHEMA. */\n schema?: string;\n /** Injectable client for hermetic tests; defaults to a DatabricksRestClient from env auth. */\n client?: WorkspaceClientLike;\n /** Injectable fetch for the OAuth M2M exchange. */\n fetchImpl?: typeof fetch;\n pollIntervalMs?: number;\n timeoutMs?: number;\n /** Drop tables seeded by this context on close. Defaults to true. */\n cleanupFixtures?: boolean;\n /** Observe submitted statements for redacted failure evidence. */\n onStatement?: (statement: { sql: string; statementId?: string }) => void;\n}\n\ninterface StatementResponse {\n statement_id?: string;\n status?: { state?: string; error?: { message?: string } };\n manifest?: { schema?: { columns?: { name: string; type_name?: string; position?: number }[] } };\n result?: { data_array?: (string | null)[][] };\n}\n\n/**\n * Live execution context on a Databricks SQL warehouse via the SQL Statement\n * Execution API (/api/2.0/sql/statements). The Statement Execution API takes\n * NAMED parameter markers, so positional `?` params are rewritten to\n * `:p1..:pn` — the ExecutionContext surface stays identical to the local\n * profile.\n */\nexport async function createWorkspaceContext(\n options: WorkspaceContextOptions = {},\n): Promise<ExecutionContext> {\n const env = options.env ?? process.env;\n const warehouseId = resolveWarehouseId(env);\n const catalog = env.DBX_TEST_CATALOG;\n const schema = options.schema ?? env.DBX_TEST_SCHEMA;\n const client = options.client ?? createClientFromEnv(env, options.fetchImpl);\n const fixtureTables = new Set<string>();\n\n const qual = (table: string) =>\n [catalog, schema, table].filter((part): part is string => Boolean(part)).join('.');\n\n async function run(sql: string, params?: readonly unknown[]): Promise<StatementResponse> {\n const { statement, parameters } = toNamedParameters(sql, params ?? []);\n const submitted = await client.post<StatementResponse>('/api/2.0/sql/statements', {\n statement,\n warehouse_id: warehouseId,\n ...(catalog ? { catalog } : {}),\n ...(schema ? { schema } : {}),\n ...(parameters.length > 0 ? { parameters } : {}),\n wait_timeout: '30s',\n on_wait_timeout: 'CONTINUE',\n });\n options.onStatement?.({\n sql,\n ...(submitted.statement_id ? { statementId: submitted.statement_id } : {}),\n });\n const state = submitted.status?.state;\n let terminal = submitted;\n if (state !== 'SUCCEEDED' && state !== 'FAILED' && state !== 'CANCELED' && state !== 'CLOSED') {\n terminal = (await waitForDatabricksStatement(\n client as DatabricksRestClient,\n submitted.statement_id ?? '',\n { pollIntervalMs: options.pollIntervalMs, timeoutMs: options.timeoutMs },\n )) as StatementResponse;\n }\n if (terminal.status?.state !== 'SUCCEEDED') {\n throw new Error(\n `Databricks statement ${terminal.status?.state ?? 'UNKNOWN'}: ${\n terminal.status?.error?.message ?? sql.slice(0, 200)\n }`,\n );\n }\n return terminal;\n }\n\n return {\n profile: 'live',\n qual,\n async exec(sql, params) {\n await run(sql, params);\n },\n async query(sql, params) {\n const response = await run(sql, params);\n return parseStatementRows(response);\n },\n async loadFixture(fixture: TableFixture) {\n if (!schema) {\n throw new Error(\n 'Live fixtures require DBX_TEST_SCHEMA (or a schema option) so tests cannot replace unscoped tables',\n );\n }\n if (!fixture.schema) {\n throw new Error(\n `Live fixtures require explicit column DDL for ${fixture.table} (schema inference is local-only)`,\n );\n }\n const columns = parseFixtureColumns(fixture.schema);\n const ddl = columns.map((c) => `\\`${c.name}\\` ${c.type}`).join(', ');\n const target = qual(fixture.table);\n await run(`CREATE OR REPLACE TABLE ${target} (${ddl}) USING DELTA`);\n fixtureTables.add(target);\n const rows = await resolveFixtureRows(fixture, columns);\n for (const row of rows) {\n const placeholders = columns\n .map((c) => (/TIMESTAMP/i.test(c.type) ? 'CAST(? AS TIMESTAMP)' : '?'))\n .join(', ');\n await run(`INSERT INTO ${qual(fixture.table)} VALUES (${placeholders})`, row);\n }\n },\n async close() {\n if (options.cleanupFixtures !== false && env.DBX_TEST_KEEP_FIXTURES !== '1') {\n for (const table of [...fixtureTables].reverse()) {\n await run(`DROP TABLE IF EXISTS ${table}`);\n }\n }\n fixtureTables.clear();\n },\n };\n}\n\n/** Rewrite positional `?` markers to the named `:pN` markers the Statement Execution API requires. */\nexport function toNamedParameters(\n sql: string,\n params: readonly unknown[],\n): { statement: string; parameters: { name: string; value: string | null }[] } {\n let out = '';\n let index = 0;\n let quote: string | null = null;\n for (let i = 0; i < sql.length; i++) {\n const ch = sql[i] as string;\n if (quote) {\n out += ch;\n if (ch === quote) quote = null;\n continue;\n }\n if (ch === \"'\" || ch === '\"' || ch === '`') {\n quote = ch;\n out += ch;\n continue;\n }\n if (ch === '?') {\n index += 1;\n out += `:p${index}`;\n continue;\n }\n out += ch;\n }\n if (index !== params.length) {\n throw new Error(`SQL has ${index} placeholders but ${params.length} params were provided`);\n }\n return {\n statement: out,\n parameters: params.map((value, i) => ({\n name: `p${i + 1}`,\n value: value === null || value === undefined ? null : String(value),\n })),\n };\n}\n\n/** Map a terminal statement response to normalized row objects. */\nexport function parseStatementRows(response: StatementResponse): Record<string, unknown>[] {\n const columns = [...(response.manifest?.schema?.columns ?? [])].sort(\n (a, b) => (a.position ?? 0) - (b.position ?? 0),\n );\n const data = response.result?.data_array ?? [];\n return data.map((tuple) => {\n const row: Record<string, unknown> = {};\n columns.forEach((column, i) => {\n row[column.name] = convertValue(tuple[i] ?? null, column.type_name ?? 'STRING');\n });\n return row;\n });\n}\n\nconst NUMERIC_TYPES = /^(INT|LONG|SHORT|BYTE|FLOAT|DOUBLE|DECIMAL)/i;\n\nfunction convertValue(value: string | null, typeName: string): unknown {\n if (value === null) return null;\n if (NUMERIC_TYPES.test(typeName)) return Number(value);\n if (/^BOOLEAN/i.test(typeName)) return value === 'true';\n return value;\n}\n\nfunction resolveWarehouseId(env: Record<string, string | undefined>): string {\n if (env.DATABRICKS_WAREHOUSE_ID) return env.DATABRICKS_WAREHOUSE_ID;\n const httpPath = env.DATABRICKS_HTTP_PATH;\n const match = httpPath?.match(/\\/warehouses\\/([\\w-]+)/);\n if (match?.[1]) return match[1];\n throw new Error(\n 'Live profile requires DATABRICKS_WAREHOUSE_ID or DATABRICKS_HTTP_PATH (/sql/1.0/warehouses/<id>)',\n );\n}\n\n/**\n * Auth precedence (ADR-0006): DATABRICKS_BEARER (pre-minted, CI) →\n * explicit env/file OIDC federation → DATABRICKS_CLIENT_ID/SECRET (OAuth\n * M2M) → DATABRICKS_TOKEN (legacy PAT).\n */\nexport function createClientFromEnv(\n env: Record<string, string | undefined>,\n fetchImpl?: typeof fetch,\n): DatabricksRestClient {\n const host = env.DATABRICKS_HOST;\n if (!host) throw new Error('Live profile requires DATABRICKS_HOST');\n return new DatabricksRestClient({\n host,\n token: resolveTokenProvider(host, env, fetchImpl),\n ...(fetchImpl ? { fetchImpl } : {}),\n });\n}\n\nexport function resolveTokenProvider(\n host: string,\n env: Record<string, string | undefined>,\n fetchImpl: typeof fetch = fetch,\n): string | (() => Promise<string>) {\n if (env.DATABRICKS_BEARER) return env.DATABRICKS_BEARER;\n const authType = env.DATABRICKS_AUTH_TYPE;\n if (authType === 'env-oidc' || authType === 'file-oidc') {\n let cached: { token: string; expiresAt: number } | null = null;\n return async () => {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n const subjectToken = await resolveFederatedSubjectToken(authType, env);\n const params = new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',\n subject_token: subjectToken,\n subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',\n scope: env.DATABRICKS_SCOPE ?? 'all-apis',\n });\n if (env.DATABRICKS_CLIENT_ID) params.set('client_id', env.DATABRICKS_CLIENT_ID);\n const response = await fetchImpl(`${host.replace(/\\/$/, '')}/oidc/v1/token`, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: params.toString(),\n });\n if (!response.ok) {\n throw new Error(`OAuth federated token exchange failed: ${response.status}`);\n }\n const body = (await response.json()) as { access_token?: unknown; expires_in?: unknown };\n if (typeof body.access_token !== 'string' || !body.access_token) {\n throw new Error('OAuth federated token exchange returned no access_token');\n }\n const expiresIn = typeof body.expires_in === 'number' ? body.expires_in : 3600;\n cached = {\n token: body.access_token,\n expiresAt: Date.now() + Math.max(1, expiresIn - 300) * 1000,\n };\n return body.access_token;\n };\n }\n const clientId = env.DATABRICKS_CLIENT_ID;\n const clientSecret = env.DATABRICKS_CLIENT_SECRET;\n if (clientId && clientSecret) {\n let cached: { token: string; expiresAt: number } | null = null;\n return async () => {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n const response = await fetchImpl(`${host.replace(/\\/$/, '')}/oidc/v1/token`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,\n },\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n scope: env.DATABRICKS_SCOPE ?? 'all-apis',\n }).toString(),\n });\n if (!response.ok) {\n throw new Error(`OAuth M2M token exchange failed: ${response.status}`);\n }\n const body = (await response.json()) as { access_token: string; expires_in?: number };\n cached = {\n token: body.access_token,\n expiresAt: Date.now() + ((body.expires_in ?? 3600) - 300) * 1000,\n };\n return body.access_token;\n };\n }\n if (env.DATABRICKS_TOKEN) return env.DATABRICKS_TOKEN;\n throw new Error(\n 'Live profile requires DATABRICKS_BEARER, env/file OIDC federation, DATABRICKS_CLIENT_ID/SECRET, or DATABRICKS_TOKEN',\n );\n}\n\nasync function resolveFederatedSubjectToken(\n authType: 'env-oidc' | 'file-oidc',\n env: Record<string, string | undefined>,\n): Promise<string> {\n if (authType === 'env-oidc') {\n const tokenEnv = env.DATABRICKS_OIDC_TOKEN_ENV ?? 'DATABRICKS_OIDC_TOKEN';\n const token = env[tokenEnv]?.trim();\n if (!token) throw new Error(`Federated OIDC token environment variable ${tokenEnv} is empty`);\n return token;\n }\n const path = env.DATABRICKS_OIDC_TOKEN_FILEPATH;\n if (!path) throw new Error('DATABRICKS_OIDC_TOKEN_FILEPATH is required for file-oidc');\n const token = (await readFile(path, 'utf8')).trim();\n if (!token) throw new Error(`Federated OIDC token file ${path} is empty`);\n return token;\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var databricks = require('@fabric-harness/databricks');
|
|
4
3
|
var promises = require('fs/promises');
|
|
4
|
+
var databricks = require('@fabric-harness/databricks');
|
|
5
5
|
var path = require('path');
|
|
6
6
|
var experimentsDbPool = require('@fabricorg/experiments-db-pool');
|
|
7
7
|
var crypto = require('crypto');
|
|
@@ -340,6 +340,39 @@ function createClientFromEnv(env, fetchImpl) {
|
|
|
340
340
|
}
|
|
341
341
|
function resolveTokenProvider(host, env, fetchImpl = fetch) {
|
|
342
342
|
if (env.DATABRICKS_BEARER) return env.DATABRICKS_BEARER;
|
|
343
|
+
const authType = env.DATABRICKS_AUTH_TYPE;
|
|
344
|
+
if (authType === "env-oidc" || authType === "file-oidc") {
|
|
345
|
+
let cached = null;
|
|
346
|
+
return async () => {
|
|
347
|
+
if (cached && Date.now() < cached.expiresAt) return cached.token;
|
|
348
|
+
const subjectToken = await resolveFederatedSubjectToken(authType, env);
|
|
349
|
+
const params = new URLSearchParams({
|
|
350
|
+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
351
|
+
subject_token: subjectToken,
|
|
352
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
|
|
353
|
+
scope: env.DATABRICKS_SCOPE ?? "all-apis"
|
|
354
|
+
});
|
|
355
|
+
if (env.DATABRICKS_CLIENT_ID) params.set("client_id", env.DATABRICKS_CLIENT_ID);
|
|
356
|
+
const response = await fetchImpl(`${host.replace(/\/$/, "")}/oidc/v1/token`, {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
359
|
+
body: params.toString()
|
|
360
|
+
});
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
throw new Error(`OAuth federated token exchange failed: ${response.status}`);
|
|
363
|
+
}
|
|
364
|
+
const body = await response.json();
|
|
365
|
+
if (typeof body.access_token !== "string" || !body.access_token) {
|
|
366
|
+
throw new Error("OAuth federated token exchange returned no access_token");
|
|
367
|
+
}
|
|
368
|
+
const expiresIn = typeof body.expires_in === "number" ? body.expires_in : 3600;
|
|
369
|
+
cached = {
|
|
370
|
+
token: body.access_token,
|
|
371
|
+
expiresAt: Date.now() + Math.max(1, expiresIn - 300) * 1e3
|
|
372
|
+
};
|
|
373
|
+
return body.access_token;
|
|
374
|
+
};
|
|
375
|
+
}
|
|
343
376
|
const clientId = env.DATABRICKS_CLIENT_ID;
|
|
344
377
|
const clientSecret = env.DATABRICKS_CLIENT_SECRET;
|
|
345
378
|
if (clientId && clientSecret) {
|
|
@@ -370,9 +403,22 @@ function resolveTokenProvider(host, env, fetchImpl = fetch) {
|
|
|
370
403
|
}
|
|
371
404
|
if (env.DATABRICKS_TOKEN) return env.DATABRICKS_TOKEN;
|
|
372
405
|
throw new Error(
|
|
373
|
-
"Live profile requires DATABRICKS_BEARER, DATABRICKS_CLIENT_ID/SECRET, or DATABRICKS_TOKEN"
|
|
406
|
+
"Live profile requires DATABRICKS_BEARER, env/file OIDC federation, DATABRICKS_CLIENT_ID/SECRET, or DATABRICKS_TOKEN"
|
|
374
407
|
);
|
|
375
408
|
}
|
|
409
|
+
async function resolveFederatedSubjectToken(authType, env) {
|
|
410
|
+
if (authType === "env-oidc") {
|
|
411
|
+
const tokenEnv = env.DATABRICKS_OIDC_TOKEN_ENV ?? "DATABRICKS_OIDC_TOKEN";
|
|
412
|
+
const token2 = env[tokenEnv]?.trim();
|
|
413
|
+
if (!token2) throw new Error(`Federated OIDC token environment variable ${tokenEnv} is empty`);
|
|
414
|
+
return token2;
|
|
415
|
+
}
|
|
416
|
+
const path = env.DATABRICKS_OIDC_TOKEN_FILEPATH;
|
|
417
|
+
if (!path) throw new Error("DATABRICKS_OIDC_TOKEN_FILEPATH is required for file-oidc");
|
|
418
|
+
const token = (await promises.readFile(path, "utf8")).trim();
|
|
419
|
+
if (!token) throw new Error(`Federated OIDC token file ${path} is empty`);
|
|
420
|
+
return token;
|
|
421
|
+
}
|
|
376
422
|
var NUMERIC_TYPES;
|
|
377
423
|
var init_workspace_context = __esm({
|
|
378
424
|
"src/workspace-context.ts"() {
|
|
@@ -562,7 +608,7 @@ async function runLiveSuite(spec, runtime = {}) {
|
|
|
562
608
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
563
609
|
const ownedWarehouse = runtime.warehouse === void 0;
|
|
564
610
|
const client = runtime.client ?? createClientFromEnv(env);
|
|
565
|
-
const warehouse = runtime.warehouse ?? await createWorkspaceContext({ env, client });
|
|
611
|
+
const warehouse = runtime.warehouse ?? (hasWarehouseConfig(env) ? await createWorkspaceContext({ env, client }) : unavailableWarehouseContext());
|
|
566
612
|
const results = [];
|
|
567
613
|
try {
|
|
568
614
|
for (const check of spec.checks) {
|
|
@@ -616,6 +662,24 @@ async function runLiveSuite(spec, runtime = {}) {
|
|
|
616
662
|
if (spec.junitPath) await writeReport(spec.junitPath, renderJUnit(evidence));
|
|
617
663
|
return evidence;
|
|
618
664
|
}
|
|
665
|
+
function hasWarehouseConfig(env) {
|
|
666
|
+
return Boolean(env.DATABRICKS_WAREHOUSE_ID || env.DATABRICKS_HTTP_PATH);
|
|
667
|
+
}
|
|
668
|
+
function unavailableWarehouseContext() {
|
|
669
|
+
const unavailable = async () => {
|
|
670
|
+
throw new Error(
|
|
671
|
+
"This live check requires DATABRICKS_WAREHOUSE_ID or DATABRICKS_HTTP_PATH; REST-only checks can run without SQL warehouse configuration"
|
|
672
|
+
);
|
|
673
|
+
};
|
|
674
|
+
return {
|
|
675
|
+
profile: "live",
|
|
676
|
+
qual: (table) => table,
|
|
677
|
+
exec: unavailable,
|
|
678
|
+
query: unavailable,
|
|
679
|
+
loadFixture: unavailable,
|
|
680
|
+
close: async () => void 0
|
|
681
|
+
};
|
|
682
|
+
}
|
|
619
683
|
async function writeReport(path$1, contents) {
|
|
620
684
|
await promises.mkdir(path.dirname(path$1), { recursive: true });
|
|
621
685
|
await promises.writeFile(path$1, contents, "utf8");
|
|
@@ -690,6 +754,7 @@ function sqlRoundTripCheck() {
|
|
|
690
754
|
return {
|
|
691
755
|
id: "sql",
|
|
692
756
|
description: "SQL Statement Execution round-trip",
|
|
757
|
+
configured: (env) => Boolean(env.DATABRICKS_WAREHOUSE_ID || env.DATABRICKS_HTTP_PATH),
|
|
693
758
|
async run({ warehouse }) {
|
|
694
759
|
const rows = await warehouse.query("SELECT 1 AS dbx_test_ok");
|
|
695
760
|
if (Number(rows[0]?.dbx_test_ok) !== 1)
|
|
@@ -1298,6 +1363,7 @@ function failureRecoveryCheck() {
|
|
|
1298
1363
|
return {
|
|
1299
1364
|
id: "failure-recovery",
|
|
1300
1365
|
description: "A failed statement is isolated and a subsequent statement recovers",
|
|
1366
|
+
configured: (env) => Boolean(env.DATABRICKS_WAREHOUSE_ID || env.DATABRICKS_HTTP_PATH),
|
|
1301
1367
|
async run({ warehouse }) {
|
|
1302
1368
|
let failedAsExpected = false;
|
|
1303
1369
|
try {
|
|
@@ -1343,6 +1409,7 @@ function backupRestoreCheck() {
|
|
|
1343
1409
|
return {
|
|
1344
1410
|
id: "backup-restore",
|
|
1345
1411
|
description: "Delta DEEP CLONE backup restores a dropped scratch table",
|
|
1412
|
+
configured: (env) => Boolean(env.DATABRICKS_WAREHOUSE_ID || env.DATABRICKS_HTTP_PATH),
|
|
1346
1413
|
async run({ warehouse }) {
|
|
1347
1414
|
const source = warehouse.qual(safeName("backup_source"));
|
|
1348
1415
|
const backup = warehouse.qual(safeName("backup_clone"));
|
|
@@ -1365,6 +1432,7 @@ function rollbackCheck() {
|
|
|
1365
1432
|
return {
|
|
1366
1433
|
id: "rollback",
|
|
1367
1434
|
description: "Delta time-travel rollback restores a known-good table version",
|
|
1435
|
+
configured: (env) => Boolean(env.DATABRICKS_WAREHOUSE_ID || env.DATABRICKS_HTTP_PATH),
|
|
1368
1436
|
async run({ warehouse }) {
|
|
1369
1437
|
const table = warehouse.qual(safeName("rollback_probe"));
|
|
1370
1438
|
try {
|
|
@@ -1453,7 +1521,10 @@ function classicComputeCheck() {
|
|
|
1453
1521
|
});
|
|
1454
1522
|
const result = run.state?.result_state;
|
|
1455
1523
|
if (result !== "SUCCESS") {
|
|
1456
|
-
|
|
1524
|
+
const message = run.state?.state_message;
|
|
1525
|
+
throw new Error(
|
|
1526
|
+
`Classic compute run ${submitted.run_id} ended in ${result ?? "UNKNOWN"}${message ? `: ${message}` : ""}`
|
|
1527
|
+
);
|
|
1457
1528
|
}
|
|
1458
1529
|
return {
|
|
1459
1530
|
runId: submitted.run_id,
|