@fabricorg/databricks-bdd 0.3.2 → 0.4.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/README.md +41 -3
- package/dist/actions-CuXXKUqe.d.ts +92 -0
- package/dist/actions-CxfxhBZh.d.cts +92 -0
- package/dist/actions.cjs +14 -0
- package/dist/actions.cjs.map +1 -0
- package/dist/actions.d.cts +6 -0
- package/dist/actions.d.ts +6 -0
- package/dist/actions.js +3 -0
- package/dist/actions.js.map +1 -0
- package/dist/cardinality.cjs +24 -0
- package/dist/cardinality.cjs.map +1 -0
- package/dist/cardinality.d.cts +19 -0
- package/dist/cardinality.d.ts +19 -0
- package/dist/cardinality.js +3 -0
- package/dist/cardinality.js.map +1 -0
- package/dist/{chunk-355WDWP3.js → chunk-3XDMBHFP.js} +101 -3
- package/dist/chunk-3XDMBHFP.js.map +1 -0
- package/dist/chunk-6JC65RH2.js +11 -0
- package/dist/chunk-6JC65RH2.js.map +1 -0
- package/dist/chunk-HTKRJLVX.js +70 -0
- package/dist/chunk-HTKRJLVX.js.map +1 -0
- package/dist/chunk-K5LJ7WSW.js +22 -0
- package/dist/chunk-K5LJ7WSW.js.map +1 -0
- package/dist/index.cjs +199 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -65
- package/dist/index.d.ts +24 -65
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/scoped-state.cjs +74 -0
- package/dist/scoped-state.cjs.map +1 -0
- package/dist/scoped-state.d.cts +30 -0
- package/dist/scoped-state.d.ts +30 -0
- package/dist/scoped-state.js +3 -0
- package/dist/scoped-state.js.map +1 -0
- package/dist/steps.cjs +177 -3
- package/dist/steps.cjs.map +1 -1
- package/dist/steps.js +10 -4
- package/dist/steps.js.map +1 -1
- package/package.json +31 -1
- package/dist/chunk-355WDWP3.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,6 +5,9 @@ Gherkin (cucumber-js) step library for testing Databricks artifacts
|
|
|
5
5
|
`DBX_TEST_PROFILE=local` and against a real SQL warehouse under
|
|
6
6
|
`DBX_TEST_PROFILE=live` (gated by `DBX_TEST_LIVE=1`). Live-only scenarios are
|
|
7
7
|
tagged (`@live`, `@jobs`, `@dlt`, `@lakebase`, `@slow`) and auto-skip locally.
|
|
8
|
+
Runtime predicates such as `@requires.cloud=azure` and Behave-compatible
|
|
9
|
+
`@use.with_cloud=azure` tags skip scenarios when configured capabilities do not
|
|
10
|
+
match.
|
|
8
11
|
|
|
9
12
|
```gherkin
|
|
10
13
|
Scenario: Seed a table and assert its contents
|
|
@@ -78,9 +81,11 @@ Then: `table {string} has {int} rows` · `table {string} contains rows matching:
|
|
|
78
81
|
The World supplies Behave-style scenario cleanup, retry-safe run/feature
|
|
79
82
|
fixtures with reverse-order `AfterAll` cleanup, typed userdata, run-level live
|
|
80
83
|
preflight, redacted failure attachments, per-step process-output capture,
|
|
81
|
-
custom parameter types,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
custom parameter types, layered run/feature/scenario state, typed action
|
|
85
|
+
composition, `?`/`*`/`+` cardinality parsing, active capability tags, Scenario
|
|
86
|
+
Outlines, serial live execution, and parallel local execution. Evidence JSON
|
|
87
|
+
schema v2 includes structured SQL, statement ID, and captured-output failure
|
|
88
|
+
records. Capture defaults to 32 KiB per step; use
|
|
84
89
|
`DBX_TEST_CAPTURE_OUTPUT=passthrough` to show output while capturing or `off` to
|
|
85
90
|
disable it. Generate the complete catalog, including unused definitions, with
|
|
86
91
|
`pnpm steps:catalog`.
|
|
@@ -97,6 +102,39 @@ cucumber-js has no safe `after_feature` event when filters, retries, and
|
|
|
97
102
|
parallel workers are involved. Use `this.addCleanup()` for scenario scope and
|
|
98
103
|
`sharedFixtures.runFixture()` for run scope.
|
|
99
104
|
|
|
105
|
+
## TypeScript parity with Behave
|
|
106
|
+
|
|
107
|
+
The Python APIs map to typed TypeScript outcomes:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import {
|
|
111
|
+
defineAction,
|
|
112
|
+
parseCardinalityField,
|
|
113
|
+
type DatabricksWorld,
|
|
114
|
+
} from '@fabricorg/databricks-bdd'
|
|
115
|
+
|
|
116
|
+
const selectColumns = defineAction<[string], string[]>((world, raw) => {
|
|
117
|
+
const columns = parseCardinalityField(raw, '+', String)
|
|
118
|
+
world.setState('scenario', 'columns', columns)
|
|
119
|
+
return columns
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Inside a step:
|
|
123
|
+
await this.runAction(selectColumns, 'subject_id, event_name')
|
|
124
|
+
this.setState('feature', 'catalog', 'fx_test')
|
|
125
|
+
const catalog = this.requireState<string>('catalog')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This supplies the behavior of `context.execute_steps()`, hierarchical user
|
|
129
|
+
context, active tags, and `cfparse` cardinality without a Python runtime.
|
|
130
|
+
Nested textual steps are intentionally represented as ordinary typed actions,
|
|
131
|
+
which remain unit-testable and preserve TypeScript stack traces.
|
|
132
|
+
|
|
133
|
+
Capability tags read `DBX_TEST_CLOUD`, `DBX_TEST_COMPUTE_MODE`,
|
|
134
|
+
`DBX_TEST_STAGE`, and `DBX_TEST_CAPABILITY_*`. Supported predicates are
|
|
135
|
+
`@requires.<key>[=<value>]`, `@excludes.<key>[=<value>]`,
|
|
136
|
+
`@use.with_<key>=<value>`, and `@not.with_<key>=<value>`.
|
|
137
|
+
|
|
100
138
|
Keep feature text engine-neutral. The shipped framework is TypeScript end to
|
|
101
139
|
end; Python adapters are optional ecosystem bridges and are not required or
|
|
102
140
|
currently included.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { World, IWorldOptions } from '@cucumber/cucumber';
|
|
2
|
+
import { TestProfile, ExecutionContext } from '@fabricorg/databricks-testkit';
|
|
3
|
+
import { FixtureSetup } from './fixtures.js';
|
|
4
|
+
import { StepOutputCapture } from './output-capture.js';
|
|
5
|
+
import { StateScope } from './scoped-state.js';
|
|
6
|
+
|
|
7
|
+
interface DatabricksWorldParameters {
|
|
8
|
+
/** Default schema for the execution context; overridable per scenario via the catalog/schema step. */
|
|
9
|
+
schema?: string;
|
|
10
|
+
/** Arbitrary typed runner configuration, comparable to behave `-D` userdata. */
|
|
11
|
+
userdata?: Record<string, string | number | boolean>;
|
|
12
|
+
}
|
|
13
|
+
type Cleanup = () => void | Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Cucumber World holding one ExecutionContext per scenario. The context is
|
|
16
|
+
* created lazily on first use so `Given catalog ... and schema ...` can run
|
|
17
|
+
* first; it is closed by the After hook registered in ./steps.ts.
|
|
18
|
+
*/
|
|
19
|
+
declare class DatabricksWorld extends World<DatabricksWorldParameters> {
|
|
20
|
+
readonly profile: TestProfile;
|
|
21
|
+
/** Rows from the last `When I run the SQL` / `When I query table` step. */
|
|
22
|
+
lastRows: Record<string, unknown>[] | null;
|
|
23
|
+
/** Error captured from the last statement, for `Then the statement fails ...`. */
|
|
24
|
+
lastError: Error | null;
|
|
25
|
+
/** Terminal run record from the last `When I run job ...` step. */
|
|
26
|
+
lastRun: Record<string, unknown> | null;
|
|
27
|
+
/** Pipeline update handle from `When I start a full refresh of pipeline ...`. */
|
|
28
|
+
lastPipelineUpdate: {
|
|
29
|
+
pipelineId: string;
|
|
30
|
+
updateId: string;
|
|
31
|
+
} | null;
|
|
32
|
+
schemaOverride: string | undefined;
|
|
33
|
+
catalogOverride: string | undefined;
|
|
34
|
+
lastSql: string | undefined;
|
|
35
|
+
lastStatementId: string | undefined;
|
|
36
|
+
featureUri: string | undefined;
|
|
37
|
+
scenarioName: string | undefined;
|
|
38
|
+
effectiveTags: readonly string[];
|
|
39
|
+
stepOutputCapture: StepOutputCapture | undefined;
|
|
40
|
+
lakebasePool: {
|
|
41
|
+
query<T extends Record<string, unknown>>(sql: string): Promise<{
|
|
42
|
+
rows: T[];
|
|
43
|
+
}>;
|
|
44
|
+
} | undefined;
|
|
45
|
+
private ctx;
|
|
46
|
+
private activeCtx;
|
|
47
|
+
private scopedState;
|
|
48
|
+
private readonly cleanups;
|
|
49
|
+
constructor(options: IWorldOptions<DatabricksWorldParameters>);
|
|
50
|
+
context(): Promise<ExecutionContext>;
|
|
51
|
+
/** Route subsequent steps through a scenario-owned secondary identity. */
|
|
52
|
+
useExecutionContext(context: ExecutionContext): void;
|
|
53
|
+
/** Register scenario-scoped cleanup. Cleanups run in reverse order. */
|
|
54
|
+
addCleanup(cleanup: Cleanup): void;
|
|
55
|
+
/** Initialize Behave-style feature/scenario metadata and layered state. */
|
|
56
|
+
initializeScenario(featureUri: string, scenarioName: string, tags: readonly string[]): void;
|
|
57
|
+
/** Set typed state at run, feature, or scenario scope. */
|
|
58
|
+
setState<T>(scope: StateScope, key: string, value: T): T;
|
|
59
|
+
/** Resolve state using scenario → feature → run precedence. */
|
|
60
|
+
getState<T>(key: string): T | undefined;
|
|
61
|
+
/** Resolve required layered state or fail with a useful message. */
|
|
62
|
+
requireState<T>(key: string): T;
|
|
63
|
+
/** Compose a reusable typed action without reparsing textual Gherkin. */
|
|
64
|
+
runAction<Args extends readonly unknown[], Result>(action: ScenarioAction<Args, Result>, ...args: Args): Promise<Awaited<Result>>;
|
|
65
|
+
/** Setup a resource once for this Cucumber run and clean it up in AfterAll. */
|
|
66
|
+
runFixture<T>(key: string, setup: FixtureSetup<T>): Promise<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Setup a resource once per feature file. It is isolated by feature URI and
|
|
69
|
+
* cleaned in AfterAll because cucumber-js has no reliable after-feature hook.
|
|
70
|
+
*/
|
|
71
|
+
featureFixture<T>(key: string, setup: FixtureSetup<T>): Promise<T>;
|
|
72
|
+
/** Resolve runner userdata from worldParameters first, then DBX_TEST_USERDATA_* env. */
|
|
73
|
+
userdata(name: string): string | number | boolean | undefined;
|
|
74
|
+
scopeTo(catalog: string, schema: string): void;
|
|
75
|
+
dispose(): Promise<void>;
|
|
76
|
+
private state;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A reusable, typed scenario action.
|
|
81
|
+
*
|
|
82
|
+
* Behave suites sometimes compose behavior with `context.execute_steps()`. In
|
|
83
|
+
* TypeScript, composing ordinary functions preserves type checking, stack
|
|
84
|
+
* traces, and direct unit-testability without reparsing Gherkin at runtime.
|
|
85
|
+
*/
|
|
86
|
+
type ScenarioAction<Args extends readonly unknown[] = readonly unknown[], Result = void> = (world: DatabricksWorld, ...args: Args) => Result | Promise<Result>;
|
|
87
|
+
/** Preserve inference while declaring a reusable scenario action. */
|
|
88
|
+
declare function defineAction<Args extends readonly unknown[], Result>(action: ScenarioAction<Args, Result>): ScenarioAction<Args, Result>;
|
|
89
|
+
/** Execute a typed action against the current scenario World. */
|
|
90
|
+
declare function runAction<Args extends readonly unknown[], Result>(world: DatabricksWorld, action: ScenarioAction<Args, Result>, ...args: Args): Promise<Awaited<Result>>;
|
|
91
|
+
|
|
92
|
+
export { DatabricksWorld as D, type ScenarioAction as S, type DatabricksWorldParameters as a, defineAction as d, runAction as r };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { World, IWorldOptions } from '@cucumber/cucumber';
|
|
2
|
+
import { TestProfile, ExecutionContext } from '@fabricorg/databricks-testkit';
|
|
3
|
+
import { FixtureSetup } from './fixtures.cjs';
|
|
4
|
+
import { StepOutputCapture } from './output-capture.cjs';
|
|
5
|
+
import { StateScope } from './scoped-state.cjs';
|
|
6
|
+
|
|
7
|
+
interface DatabricksWorldParameters {
|
|
8
|
+
/** Default schema for the execution context; overridable per scenario via the catalog/schema step. */
|
|
9
|
+
schema?: string;
|
|
10
|
+
/** Arbitrary typed runner configuration, comparable to behave `-D` userdata. */
|
|
11
|
+
userdata?: Record<string, string | number | boolean>;
|
|
12
|
+
}
|
|
13
|
+
type Cleanup = () => void | Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Cucumber World holding one ExecutionContext per scenario. The context is
|
|
16
|
+
* created lazily on first use so `Given catalog ... and schema ...` can run
|
|
17
|
+
* first; it is closed by the After hook registered in ./steps.ts.
|
|
18
|
+
*/
|
|
19
|
+
declare class DatabricksWorld extends World<DatabricksWorldParameters> {
|
|
20
|
+
readonly profile: TestProfile;
|
|
21
|
+
/** Rows from the last `When I run the SQL` / `When I query table` step. */
|
|
22
|
+
lastRows: Record<string, unknown>[] | null;
|
|
23
|
+
/** Error captured from the last statement, for `Then the statement fails ...`. */
|
|
24
|
+
lastError: Error | null;
|
|
25
|
+
/** Terminal run record from the last `When I run job ...` step. */
|
|
26
|
+
lastRun: Record<string, unknown> | null;
|
|
27
|
+
/** Pipeline update handle from `When I start a full refresh of pipeline ...`. */
|
|
28
|
+
lastPipelineUpdate: {
|
|
29
|
+
pipelineId: string;
|
|
30
|
+
updateId: string;
|
|
31
|
+
} | null;
|
|
32
|
+
schemaOverride: string | undefined;
|
|
33
|
+
catalogOverride: string | undefined;
|
|
34
|
+
lastSql: string | undefined;
|
|
35
|
+
lastStatementId: string | undefined;
|
|
36
|
+
featureUri: string | undefined;
|
|
37
|
+
scenarioName: string | undefined;
|
|
38
|
+
effectiveTags: readonly string[];
|
|
39
|
+
stepOutputCapture: StepOutputCapture | undefined;
|
|
40
|
+
lakebasePool: {
|
|
41
|
+
query<T extends Record<string, unknown>>(sql: string): Promise<{
|
|
42
|
+
rows: T[];
|
|
43
|
+
}>;
|
|
44
|
+
} | undefined;
|
|
45
|
+
private ctx;
|
|
46
|
+
private activeCtx;
|
|
47
|
+
private scopedState;
|
|
48
|
+
private readonly cleanups;
|
|
49
|
+
constructor(options: IWorldOptions<DatabricksWorldParameters>);
|
|
50
|
+
context(): Promise<ExecutionContext>;
|
|
51
|
+
/** Route subsequent steps through a scenario-owned secondary identity. */
|
|
52
|
+
useExecutionContext(context: ExecutionContext): void;
|
|
53
|
+
/** Register scenario-scoped cleanup. Cleanups run in reverse order. */
|
|
54
|
+
addCleanup(cleanup: Cleanup): void;
|
|
55
|
+
/** Initialize Behave-style feature/scenario metadata and layered state. */
|
|
56
|
+
initializeScenario(featureUri: string, scenarioName: string, tags: readonly string[]): void;
|
|
57
|
+
/** Set typed state at run, feature, or scenario scope. */
|
|
58
|
+
setState<T>(scope: StateScope, key: string, value: T): T;
|
|
59
|
+
/** Resolve state using scenario → feature → run precedence. */
|
|
60
|
+
getState<T>(key: string): T | undefined;
|
|
61
|
+
/** Resolve required layered state or fail with a useful message. */
|
|
62
|
+
requireState<T>(key: string): T;
|
|
63
|
+
/** Compose a reusable typed action without reparsing textual Gherkin. */
|
|
64
|
+
runAction<Args extends readonly unknown[], Result>(action: ScenarioAction<Args, Result>, ...args: Args): Promise<Awaited<Result>>;
|
|
65
|
+
/** Setup a resource once for this Cucumber run and clean it up in AfterAll. */
|
|
66
|
+
runFixture<T>(key: string, setup: FixtureSetup<T>): Promise<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Setup a resource once per feature file. It is isolated by feature URI and
|
|
69
|
+
* cleaned in AfterAll because cucumber-js has no reliable after-feature hook.
|
|
70
|
+
*/
|
|
71
|
+
featureFixture<T>(key: string, setup: FixtureSetup<T>): Promise<T>;
|
|
72
|
+
/** Resolve runner userdata from worldParameters first, then DBX_TEST_USERDATA_* env. */
|
|
73
|
+
userdata(name: string): string | number | boolean | undefined;
|
|
74
|
+
scopeTo(catalog: string, schema: string): void;
|
|
75
|
+
dispose(): Promise<void>;
|
|
76
|
+
private state;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A reusable, typed scenario action.
|
|
81
|
+
*
|
|
82
|
+
* Behave suites sometimes compose behavior with `context.execute_steps()`. In
|
|
83
|
+
* TypeScript, composing ordinary functions preserves type checking, stack
|
|
84
|
+
* traces, and direct unit-testability without reparsing Gherkin at runtime.
|
|
85
|
+
*/
|
|
86
|
+
type ScenarioAction<Args extends readonly unknown[] = readonly unknown[], Result = void> = (world: DatabricksWorld, ...args: Args) => Result | Promise<Result>;
|
|
87
|
+
/** Preserve inference while declaring a reusable scenario action. */
|
|
88
|
+
declare function defineAction<Args extends readonly unknown[], Result>(action: ScenarioAction<Args, Result>): ScenarioAction<Args, Result>;
|
|
89
|
+
/** Execute a typed action against the current scenario World. */
|
|
90
|
+
declare function runAction<Args extends readonly unknown[], Result>(world: DatabricksWorld, action: ScenarioAction<Args, Result>, ...args: Args): Promise<Awaited<Result>>;
|
|
91
|
+
|
|
92
|
+
export { DatabricksWorld as D, type ScenarioAction as S, type DatabricksWorldParameters as a, defineAction as d, runAction as r };
|
package/dist/actions.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/actions.ts
|
|
4
|
+
function defineAction(action) {
|
|
5
|
+
return action;
|
|
6
|
+
}
|
|
7
|
+
async function runAction(world, action, ...args) {
|
|
8
|
+
return await action(world, ...args);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports.defineAction = defineAction;
|
|
12
|
+
exports.runAction = runAction;
|
|
13
|
+
//# sourceMappingURL=actions.cjs.map
|
|
14
|
+
//# sourceMappingURL=actions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/actions.ts"],"names":[],"mappings":";;;AAeO,SAAS,aACd,MAAA,EAC8B;AAC9B,EAAA,OAAO,MAAA;AACT;AAGA,eAAsB,SAAA,CACpB,KAAA,EACA,MAAA,EAAA,GACG,IAAA,EACuB;AAC1B,EAAA,OAAQ,MAAM,MAAA,CAAO,KAAA,EAAO,GAAG,IAAI,CAAA;AACrC","file":"actions.cjs","sourcesContent":["import type { DatabricksWorld } from './world.js';\n\n/**\n * A reusable, typed scenario action.\n *\n * Behave suites sometimes compose behavior with `context.execute_steps()`. In\n * TypeScript, composing ordinary functions preserves type checking, stack\n * traces, and direct unit-testability without reparsing Gherkin at runtime.\n */\nexport type ScenarioAction<Args extends readonly unknown[] = readonly unknown[], Result = void> = (\n world: DatabricksWorld,\n ...args: Args\n) => Result | Promise<Result>;\n\n/** Preserve inference while declaring a reusable scenario action. */\nexport function defineAction<Args extends readonly unknown[], Result>(\n action: ScenarioAction<Args, Result>,\n): ScenarioAction<Args, Result> {\n return action;\n}\n\n/** Execute a typed action against the current scenario World. */\nexport async function runAction<Args extends readonly unknown[], Result>(\n world: DatabricksWorld,\n action: ScenarioAction<Args, Result>,\n ...args: Args\n): Promise<Awaited<Result>> {\n return (await action(world, ...args)) as Awaited<Result>;\n}\n"]}
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"actions.js"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/cardinality.ts
|
|
4
|
+
function parseCardinalityField(value, cardinality, transform, options = {}) {
|
|
5
|
+
const fieldName = options.fieldName ?? "field";
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
const items = trimmed ? trimmed.split(options.separator ?? /\s*,\s*/).map((item) => item.trim()) : [];
|
|
8
|
+
if (items.some((item) => !item)) {
|
|
9
|
+
throw new Error(`${fieldName} contains an empty value`);
|
|
10
|
+
}
|
|
11
|
+
if (cardinality === "?" && items.length > 1) {
|
|
12
|
+
throw new Error(`${fieldName} expects zero or one value, received ${items.length}`);
|
|
13
|
+
}
|
|
14
|
+
if (cardinality === "+" && items.length === 0) {
|
|
15
|
+
throw new Error(`${fieldName} expects one or more values`);
|
|
16
|
+
}
|
|
17
|
+
const parsed = items.map(transform);
|
|
18
|
+
if (cardinality === "?") return parsed[0];
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.parseCardinalityField = parseCardinalityField;
|
|
23
|
+
//# sourceMappingURL=cardinality.cjs.map
|
|
24
|
+
//# sourceMappingURL=cardinality.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cardinality.ts"],"names":[],"mappings":";;;AAmBO,SAAS,sBACd,KAAA,EACA,WAAA,EACA,SAAA,EACA,OAAA,GAA8B,EAAC,EACN;AACzB,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,OAAA;AACvC,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,EAAA,MAAM,KAAA,GAAQ,OAAA,GACV,OAAA,CAAQ,KAAA,CAAM,QAAQ,SAAA,IAAa,SAAS,CAAA,CAAE,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,IAAA,EAAM,IACvE,EAAC;AACL,EAAA,IAAI,MAAM,IAAA,CAAK,CAAC,IAAA,KAAS,CAAC,IAAI,CAAA,EAAG;AAC/B,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,wBAAA,CAA0B,CAAA;AAAA,EACxD;AAEA,EAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG;AAC3C,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,qCAAA,EAAwC,KAAA,CAAM,MAAM,CAAA,CAAE,CAAA;AAAA,EACpF;AACA,EAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG;AAC7C,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,2BAAA,CAA6B,CAAA;AAAA,EAC3D;AAEA,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AAClC,EAAA,IAAI,WAAA,KAAgB,GAAA,EAAK,OAAO,MAAA,CAAO,CAAC,CAAA;AACxC,EAAA,OAAO,MAAA;AACT","file":"cardinality.cjs","sourcesContent":["/** Cardinalities supported by Behave's cfparse fields. */\nexport type FieldCardinality = '?' | '*' | '+';\n\nexport interface CardinalityOptions {\n /** Delimiter between values. Defaults to a comma with optional whitespace. */\n separator?: string | RegExp;\n /** Human-readable field name used in validation errors. */\n fieldName?: string;\n}\n\nexport type CardinalityResult<T, C extends FieldCardinality> = C extends '?' ? T | undefined : T[];\n\n/**\n * Parse a Cucumber string parameter with cfparse-compatible cardinality.\n *\n * Use `?` for zero-or-one, `*` for zero-or-more, and `+` for one-or-more.\n * This keeps the capability in TypeScript while allowing standard Cucumber\n * Expressions and readable comma-separated parameters.\n */\nexport function parseCardinalityField<T, C extends FieldCardinality>(\n value: string,\n cardinality: C,\n transform: (item: string, index: number) => T,\n options: CardinalityOptions = {},\n): CardinalityResult<T, C> {\n const fieldName = options.fieldName ?? 'field';\n const trimmed = value.trim();\n const items = trimmed\n ? trimmed.split(options.separator ?? /\\s*,\\s*/).map((item) => item.trim())\n : [];\n if (items.some((item) => !item)) {\n throw new Error(`${fieldName} contains an empty value`);\n }\n\n if (cardinality === '?' && items.length > 1) {\n throw new Error(`${fieldName} expects zero or one value, received ${items.length}`);\n }\n if (cardinality === '+' && items.length === 0) {\n throw new Error(`${fieldName} expects one or more values`);\n }\n\n const parsed = items.map(transform);\n if (cardinality === '?') return parsed[0] as CardinalityResult<T, C>;\n return parsed as CardinalityResult<T, C>;\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Cardinalities supported by Behave's cfparse fields. */
|
|
2
|
+
type FieldCardinality = '?' | '*' | '+';
|
|
3
|
+
interface CardinalityOptions {
|
|
4
|
+
/** Delimiter between values. Defaults to a comma with optional whitespace. */
|
|
5
|
+
separator?: string | RegExp;
|
|
6
|
+
/** Human-readable field name used in validation errors. */
|
|
7
|
+
fieldName?: string;
|
|
8
|
+
}
|
|
9
|
+
type CardinalityResult<T, C extends FieldCardinality> = C extends '?' ? T | undefined : T[];
|
|
10
|
+
/**
|
|
11
|
+
* Parse a Cucumber string parameter with cfparse-compatible cardinality.
|
|
12
|
+
*
|
|
13
|
+
* Use `?` for zero-or-one, `*` for zero-or-more, and `+` for one-or-more.
|
|
14
|
+
* This keeps the capability in TypeScript while allowing standard Cucumber
|
|
15
|
+
* Expressions and readable comma-separated parameters.
|
|
16
|
+
*/
|
|
17
|
+
declare function parseCardinalityField<T, C extends FieldCardinality>(value: string, cardinality: C, transform: (item: string, index: number) => T, options?: CardinalityOptions): CardinalityResult<T, C>;
|
|
18
|
+
|
|
19
|
+
export { type CardinalityOptions, type CardinalityResult, type FieldCardinality, parseCardinalityField };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Cardinalities supported by Behave's cfparse fields. */
|
|
2
|
+
type FieldCardinality = '?' | '*' | '+';
|
|
3
|
+
interface CardinalityOptions {
|
|
4
|
+
/** Delimiter between values. Defaults to a comma with optional whitespace. */
|
|
5
|
+
separator?: string | RegExp;
|
|
6
|
+
/** Human-readable field name used in validation errors. */
|
|
7
|
+
fieldName?: string;
|
|
8
|
+
}
|
|
9
|
+
type CardinalityResult<T, C extends FieldCardinality> = C extends '?' ? T | undefined : T[];
|
|
10
|
+
/**
|
|
11
|
+
* Parse a Cucumber string parameter with cfparse-compatible cardinality.
|
|
12
|
+
*
|
|
13
|
+
* Use `?` for zero-or-one, `*` for zero-or-more, and `+` for one-or-more.
|
|
14
|
+
* This keeps the capability in TypeScript while allowing standard Cucumber
|
|
15
|
+
* Expressions and readable comma-separated parameters.
|
|
16
|
+
*/
|
|
17
|
+
declare function parseCardinalityField<T, C extends FieldCardinality>(value: string, cardinality: C, transform: (item: string, index: number) => T, options?: CardinalityOptions): CardinalityResult<T, C>;
|
|
18
|
+
|
|
19
|
+
export { type CardinalityOptions, type CardinalityResult, type FieldCardinality, parseCardinalityField };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"cardinality.js"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { runAction } from './chunk-6JC65RH2.js';
|
|
1
2
|
import { sharedFixtures } from './chunk-SZL3KWW7.js';
|
|
3
|
+
import { ScopedState, sharedState } from './chunk-HTKRJLVX.js';
|
|
2
4
|
import { World } from '@cucumber/cucumber';
|
|
3
5
|
import { resolveProfile, createExecutionContext } from '@fabricorg/databricks-testkit';
|
|
4
6
|
export { waitForPipelineUpdate } from '@fabricorg/databricks-testkit';
|
|
@@ -19,10 +21,13 @@ var DatabricksWorld = class extends World {
|
|
|
19
21
|
lastSql;
|
|
20
22
|
lastStatementId;
|
|
21
23
|
featureUri;
|
|
24
|
+
scenarioName;
|
|
25
|
+
effectiveTags = [];
|
|
22
26
|
stepOutputCapture;
|
|
23
27
|
lakebasePool;
|
|
24
28
|
ctx;
|
|
25
29
|
activeCtx;
|
|
30
|
+
scopedState;
|
|
26
31
|
cleanups = [];
|
|
27
32
|
constructor(options) {
|
|
28
33
|
super(options);
|
|
@@ -57,6 +62,29 @@ var DatabricksWorld = class extends World {
|
|
|
57
62
|
addCleanup(cleanup) {
|
|
58
63
|
this.cleanups.push(cleanup);
|
|
59
64
|
}
|
|
65
|
+
/** Initialize Behave-style feature/scenario metadata and layered state. */
|
|
66
|
+
initializeScenario(featureUri, scenarioName, tags) {
|
|
67
|
+
this.featureUri = featureUri;
|
|
68
|
+
this.scenarioName = scenarioName;
|
|
69
|
+
this.effectiveTags = [...tags];
|
|
70
|
+
this.scopedState = new ScopedState(sharedState.run(), sharedState.feature(featureUri));
|
|
71
|
+
}
|
|
72
|
+
/** Set typed state at run, feature, or scenario scope. */
|
|
73
|
+
setState(scope, key, value) {
|
|
74
|
+
return this.state().set(scope, key, value);
|
|
75
|
+
}
|
|
76
|
+
/** Resolve state using scenario → feature → run precedence. */
|
|
77
|
+
getState(key) {
|
|
78
|
+
return this.state().get(key);
|
|
79
|
+
}
|
|
80
|
+
/** Resolve required layered state or fail with a useful message. */
|
|
81
|
+
requireState(key) {
|
|
82
|
+
return this.state().require(key);
|
|
83
|
+
}
|
|
84
|
+
/** Compose a reusable typed action without reparsing textual Gherkin. */
|
|
85
|
+
runAction(action, ...args) {
|
|
86
|
+
return runAction(this, action, ...args);
|
|
87
|
+
}
|
|
60
88
|
/** Setup a resource once for this Cucumber run and clean it up in AfterAll. */
|
|
61
89
|
runFixture(key, setup) {
|
|
62
90
|
return sharedFixtures.runFixture(key, setup);
|
|
@@ -103,8 +131,16 @@ var DatabricksWorld = class extends World {
|
|
|
103
131
|
errors.push(error);
|
|
104
132
|
}
|
|
105
133
|
this.ctx = void 0;
|
|
134
|
+
this.scopedState?.clearScenario();
|
|
135
|
+
this.scopedState = void 0;
|
|
106
136
|
if (errors.length > 0) throw new AggregateError(errors, "Scenario cleanup failed");
|
|
107
137
|
}
|
|
138
|
+
state() {
|
|
139
|
+
if (!this.scopedState) {
|
|
140
|
+
throw new Error("BDD scoped state is only available after the scenario Before hook");
|
|
141
|
+
}
|
|
142
|
+
return this.scopedState;
|
|
143
|
+
}
|
|
108
144
|
};
|
|
109
145
|
|
|
110
146
|
// src/profile.ts
|
|
@@ -123,6 +159,68 @@ function liveOnlyReason(tags, profile) {
|
|
|
123
159
|
if (blocking.length === 0) return null;
|
|
124
160
|
return `requires a live workspace (${blocking.join(", ")}); running under DBX_TEST_PROFILE=local`;
|
|
125
161
|
}
|
|
162
|
+
function capabilitiesFromEnv(profile, env = process.env) {
|
|
163
|
+
const capabilities = { profile };
|
|
164
|
+
const builtins = [
|
|
165
|
+
["cloud", env.DBX_TEST_CLOUD],
|
|
166
|
+
["compute", env.DBX_TEST_COMPUTE_MODE ?? env.DBX_TEST_COMPUTE],
|
|
167
|
+
["stage", env.DBX_TEST_STAGE]
|
|
168
|
+
];
|
|
169
|
+
for (const [key, value] of builtins) {
|
|
170
|
+
if (value !== void 0 && value !== "") capabilities[key] = value;
|
|
171
|
+
}
|
|
172
|
+
for (const [name, value] of Object.entries(env)) {
|
|
173
|
+
if (!name.startsWith("DBX_TEST_CAPABILITY_") || value === void 0) continue;
|
|
174
|
+
capabilities[normalizeCapabilityKey(name.slice("DBX_TEST_CAPABILITY_".length))] = value;
|
|
175
|
+
}
|
|
176
|
+
return capabilities;
|
|
177
|
+
}
|
|
178
|
+
function capabilityTagReason(tags, capabilities) {
|
|
179
|
+
const normalized = new Map(
|
|
180
|
+
Object.entries(capabilities).map(([key, value]) => [
|
|
181
|
+
normalizeCapabilityKey(key),
|
|
182
|
+
String(value).toLowerCase()
|
|
183
|
+
])
|
|
184
|
+
);
|
|
185
|
+
for (const tag of tags) {
|
|
186
|
+
const predicate = parseCapabilityTag(tag);
|
|
187
|
+
if (!predicate) continue;
|
|
188
|
+
const actual = normalized.get(normalizeCapabilityKey(predicate.key));
|
|
189
|
+
const matches = predicate.expected ? actual === predicate.expected.toLowerCase() : actual !== void 0 && !["", "0", "false", "no", "off"].includes(actual);
|
|
190
|
+
if (predicate.kind === "requires" && !matches) {
|
|
191
|
+
return `${tag} requires capability ${predicate.key}${predicate.expected ? `=${predicate.expected}` : ""}; actual ${actual ?? "unset"}`;
|
|
192
|
+
}
|
|
193
|
+
if (predicate.kind === "excludes" && matches) {
|
|
194
|
+
return `${tag} excludes capability ${predicate.key}${predicate.expected ? `=${predicate.expected}` : ""}`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
function scenarioSkipReason(tags, profile, env = process.env) {
|
|
200
|
+
return liveOnlyReason(tags, profile) ?? capabilityTagReason(tags, capabilitiesFromEnv(profile, env));
|
|
201
|
+
}
|
|
202
|
+
function parseCapabilityTag(tag) {
|
|
203
|
+
const direct = tag.match(/^@(requires|excludes)\.([^=]+)(?:=(.+))?$/i);
|
|
204
|
+
if (direct?.[1] && direct[2]) {
|
|
205
|
+
return {
|
|
206
|
+
kind: direct[1].toLowerCase(),
|
|
207
|
+
key: direct[2],
|
|
208
|
+
...direct[3] ? { expected: direct[3] } : {}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const behave = tag.match(/^@(use|not)\.with_([^=]+)=(.+)$/i);
|
|
212
|
+
if (behave?.[1] && behave[2] && behave[3]) {
|
|
213
|
+
return {
|
|
214
|
+
kind: behave[1].toLowerCase() === "use" ? "requires" : "excludes",
|
|
215
|
+
key: behave[2],
|
|
216
|
+
expected: behave[3]
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
function normalizeCapabilityKey(key) {
|
|
222
|
+
return key.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
223
|
+
}
|
|
126
224
|
|
|
127
225
|
// src/infer.ts
|
|
128
226
|
var INT_RE = /^-?\d+$/;
|
|
@@ -212,6 +310,6 @@ async function startPipelineUpdate(client, pipelineId, options = {}) {
|
|
|
212
310
|
return response.update_id;
|
|
213
311
|
}
|
|
214
312
|
|
|
215
|
-
export { DatabricksWorld, LIVE_ONLY_TAGS, coerceMatchRows, inferFixture, liveOnlyReason, resolveJobId, resolvePipelineId, runJobToTerminal, startPipelineUpdate };
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
217
|
-
//# sourceMappingURL=chunk-
|
|
313
|
+
export { DatabricksWorld, LIVE_ONLY_TAGS, capabilitiesFromEnv, capabilityTagReason, coerceMatchRows, inferFixture, liveOnlyReason, resolveJobId, resolvePipelineId, runJobToTerminal, scenarioSkipReason, startPipelineUpdate };
|
|
314
|
+
//# sourceMappingURL=chunk-3XDMBHFP.js.map
|
|
315
|
+
//# sourceMappingURL=chunk-3XDMBHFP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/world.ts","../src/profile.ts","../src/infer.ts","../src/jobs.ts","../src/pipelines.ts"],"names":[],"mappings":"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","file":"chunk-3XDMBHFP.js","sourcesContent":["import { type IWorldOptions, World } from '@cucumber/cucumber';\nimport {\n type ExecutionContext,\n type TestProfile,\n createExecutionContext,\n resolveProfile,\n} from '@fabricorg/databricks-testkit';\nimport { type ScenarioAction, runAction as executeAction } from './actions.js';\nimport { type FixtureSetup, sharedFixtures } from './fixtures.js';\nimport type { StepOutputCapture } from './output-capture.js';\nimport { ScopedState, type StateScope, sharedState } from './scoped-state.js';\n\nexport interface DatabricksWorldParameters {\n /** Default schema for the execution context; overridable per scenario via the catalog/schema step. */\n schema?: string;\n /** Arbitrary typed runner configuration, comparable to behave `-D` userdata. */\n userdata?: Record<string, string | number | boolean>;\n}\n\ntype Cleanup = () => void | Promise<void>;\n\n/**\n * Cucumber World holding one ExecutionContext per scenario. The context is\n * created lazily on first use so `Given catalog ... and schema ...` can run\n * first; it is closed by the After hook registered in ./steps.ts.\n */\nexport class DatabricksWorld extends World<DatabricksWorldParameters> {\n readonly profile: TestProfile;\n /** Rows from the last `When I run the SQL` / `When I query table` step. */\n lastRows: Record<string, unknown>[] | null = null;\n /** Error captured from the last statement, for `Then the statement fails ...`. */\n lastError: Error | null = null;\n /** Terminal run record from the last `When I run job ...` step. */\n lastRun: Record<string, unknown> | null = null;\n /** Pipeline update handle from `When I start a full refresh of pipeline ...`. */\n lastPipelineUpdate: { pipelineId: string; updateId: string } | null = null;\n schemaOverride: string | undefined;\n catalogOverride: string | undefined;\n lastSql: string | undefined;\n lastStatementId: string | undefined;\n featureUri: string | undefined;\n scenarioName: string | undefined;\n effectiveTags: readonly string[] = [];\n stepOutputCapture: StepOutputCapture | undefined;\n lakebasePool:\n | { query<T extends Record<string, unknown>>(sql: string): Promise<{ rows: T[] }> }\n | undefined;\n private ctx: ExecutionContext | undefined;\n private activeCtx: ExecutionContext | undefined;\n private scopedState: ScopedState | undefined;\n private readonly cleanups: Cleanup[] = [];\n\n constructor(options: IWorldOptions<DatabricksWorldParameters>) {\n super(options);\n this.profile = resolveProfile();\n }\n\n async context(): Promise<ExecutionContext> {\n if (this.activeCtx) return this.activeCtx;\n if (!this.ctx) {\n const env = this.catalogOverride\n ? { ...process.env, DBX_TEST_CATALOG: this.catalogOverride }\n : process.env;\n this.ctx = await createExecutionContext({\n profile: this.profile,\n env,\n schema: this.schemaOverride ?? this.parameters.schema,\n onStatement: ({ sql, statementId }) => {\n this.lastSql = sql;\n this.lastStatementId = statementId;\n },\n });\n }\n return this.ctx;\n }\n\n /** Route subsequent steps through a scenario-owned secondary identity. */\n useExecutionContext(context: ExecutionContext): void {\n if (this.activeCtx) throw new Error('A secondary execution context is already active');\n this.activeCtx = context;\n this.addCleanup(async () => {\n await context.close();\n this.activeCtx = undefined;\n });\n }\n\n /** Register scenario-scoped cleanup. Cleanups run in reverse order. */\n addCleanup(cleanup: Cleanup): void {\n this.cleanups.push(cleanup);\n }\n\n /** Initialize Behave-style feature/scenario metadata and layered state. */\n initializeScenario(featureUri: string, scenarioName: string, tags: readonly string[]): void {\n this.featureUri = featureUri;\n this.scenarioName = scenarioName;\n this.effectiveTags = [...tags];\n this.scopedState = new ScopedState(sharedState.run(), sharedState.feature(featureUri));\n }\n\n /** Set typed state at run, feature, or scenario scope. */\n setState<T>(scope: StateScope, key: string, value: T): T {\n return this.state().set(scope, key, value);\n }\n\n /** Resolve state using scenario → feature → run precedence. */\n getState<T>(key: string): T | undefined {\n return this.state().get<T>(key);\n }\n\n /** Resolve required layered state or fail with a useful message. */\n requireState<T>(key: string): T {\n return this.state().require<T>(key);\n }\n\n /** Compose a reusable typed action without reparsing textual Gherkin. */\n runAction<Args extends readonly unknown[], Result>(\n action: ScenarioAction<Args, Result>,\n ...args: Args\n ): Promise<Awaited<Result>> {\n return executeAction(this, action, ...args);\n }\n\n /** Setup a resource once for this Cucumber run and clean it up in AfterAll. */\n runFixture<T>(key: string, setup: FixtureSetup<T>): Promise<T> {\n return sharedFixtures.runFixture(key, setup);\n }\n\n /**\n * Setup a resource once per feature file. It is isolated by feature URI and\n * cleaned in AfterAll because cucumber-js has no reliable after-feature hook.\n */\n featureFixture<T>(key: string, setup: FixtureSetup<T>): Promise<T> {\n if (!this.featureUri) {\n throw new Error('featureFixture is only available after the scenario Before hook');\n }\n return sharedFixtures.featureFixture(this.featureUri, key, setup);\n }\n\n /** Resolve runner userdata from worldParameters first, then DBX_TEST_USERDATA_* env. */\n userdata(name: string): string | number | boolean | undefined {\n const configured = this.parameters.userdata?.[name];\n if (configured !== undefined) return configured;\n return process.env[`DBX_TEST_USERDATA_${name.replace(/[^A-Za-z0-9]/g, '_').toUpperCase()}`];\n }\n\n scopeTo(catalog: string, schema: string): void {\n if (this.ctx) {\n throw new Error(\n 'Given catalog/schema must run before any step that touches the warehouse in the scenario',\n );\n }\n // Feature files stay portable and readable with illustrative names. A live\n // runner may redirect every scenario into its provisioned scratch scope.\n this.catalogOverride =\n this.profile === 'live' ? (process.env.DBX_TEST_CATALOG ?? catalog) : catalog;\n this.schemaOverride =\n this.profile === 'live' ? (process.env.DBX_TEST_SCHEMA ?? schema) : schema;\n }\n\n async dispose(): Promise<void> {\n this.stepOutputCapture?.stop();\n this.stepOutputCapture = undefined;\n const errors: unknown[] = [];\n for (const cleanup of this.cleanups.splice(0).reverse()) {\n try {\n await cleanup();\n } catch (error) {\n errors.push(error);\n }\n }\n try {\n await this.ctx?.close();\n } catch (error) {\n errors.push(error);\n }\n this.ctx = undefined;\n this.scopedState?.clearScenario();\n this.scopedState = undefined;\n if (errors.length > 0) throw new AggregateError(errors, 'Scenario cleanup failed');\n }\n\n private state(): ScopedState {\n if (!this.scopedState) {\n throw new Error('BDD scoped state is only available after the scenario Before hook');\n }\n return this.scopedState;\n }\n}\n","import type { TestProfile } from '@fabricorg/databricks-testkit';\n\n/**\n * Tags that require a live workspace (or a long budget). Scenarios carrying\n * any of them are skipped under the `local` profile — see ADR-0006.\n */\nexport const LIVE_ONLY_TAGS = [\n '@live',\n '@jobs',\n '@dlt',\n '@pipeline',\n '@autoloader',\n '@lakebase',\n '@slow',\n] as const;\n\n/** Returns a skip reason when the scenario cannot run under the given profile, else null. */\nexport function liveOnlyReason(tags: readonly string[], profile: TestProfile): string | null {\n if (profile === 'live') return null;\n const blocking = tags.filter((t) => (LIVE_ONLY_TAGS as readonly string[]).includes(t));\n if (blocking.length === 0) return null;\n return `requires a live workspace (${blocking.join(', ')}); running under DBX_TEST_PROFILE=local`;\n}\n\nexport type CapabilityValue = string | number | boolean;\nexport type CapabilityMap = Readonly<Record<string, CapabilityValue>>;\n\n/** Build the runtime capability map used by active-tag predicates. */\nexport function capabilitiesFromEnv(\n profile: TestProfile,\n env: NodeJS.ProcessEnv = process.env,\n): CapabilityMap {\n const capabilities: Record<string, CapabilityValue> = { profile };\n const builtins: Array<[string, string | undefined]> = [\n ['cloud', env.DBX_TEST_CLOUD],\n ['compute', env.DBX_TEST_COMPUTE_MODE ?? env.DBX_TEST_COMPUTE],\n ['stage', env.DBX_TEST_STAGE],\n ];\n for (const [key, value] of builtins) {\n if (value !== undefined && value !== '') capabilities[key] = value;\n }\n for (const [name, value] of Object.entries(env)) {\n if (!name.startsWith('DBX_TEST_CAPABILITY_') || value === undefined) continue;\n capabilities[normalizeCapabilityKey(name.slice('DBX_TEST_CAPABILITY_'.length))] = value;\n }\n return capabilities;\n}\n\n/**\n * Evaluate TypeScript-native active tags.\n *\n * Supported forms:\n * - `@requires.cloud=azure` / `@requires.serverless`\n * - `@excludes.compute=classic`\n * - Behave-compatible aliases `@use.with_cloud=azure` and\n * `@not.with_cloud=azure`\n */\nexport function capabilityTagReason(\n tags: readonly string[],\n capabilities: CapabilityMap,\n): string | null {\n const normalized = new Map(\n Object.entries(capabilities).map(([key, value]) => [\n normalizeCapabilityKey(key),\n String(value).toLowerCase(),\n ]),\n );\n\n for (const tag of tags) {\n const predicate = parseCapabilityTag(tag);\n if (!predicate) continue;\n const actual = normalized.get(normalizeCapabilityKey(predicate.key));\n const matches = predicate.expected\n ? actual === predicate.expected.toLowerCase()\n : actual !== undefined && !['', '0', 'false', 'no', 'off'].includes(actual);\n if (predicate.kind === 'requires' && !matches) {\n return `${tag} requires capability ${predicate.key}${predicate.expected ? `=${predicate.expected}` : ''}; actual ${actual ?? 'unset'}`;\n }\n if (predicate.kind === 'excludes' && matches) {\n return `${tag} excludes capability ${predicate.key}${predicate.expected ? `=${predicate.expected}` : ''}`;\n }\n }\n return null;\n}\n\n/** Combined local/live and active-capability skip policy. */\nexport function scenarioSkipReason(\n tags: readonly string[],\n profile: TestProfile,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n return (\n liveOnlyReason(tags, profile) ?? capabilityTagReason(tags, capabilitiesFromEnv(profile, env))\n );\n}\n\nfunction parseCapabilityTag(\n tag: string,\n): { kind: 'requires' | 'excludes'; key: string; expected?: string } | undefined {\n const direct = tag.match(/^@(requires|excludes)\\.([^=]+)(?:=(.+))?$/i);\n if (direct?.[1] && direct[2]) {\n return {\n kind: direct[1].toLowerCase() as 'requires' | 'excludes',\n key: direct[2],\n ...(direct[3] ? { expected: direct[3] } : {}),\n };\n }\n const behave = tag.match(/^@(use|not)\\.with_([^=]+)=(.+)$/i);\n if (behave?.[1] && behave[2] && behave[3]) {\n return {\n kind: behave[1].toLowerCase() === 'use' ? 'requires' : 'excludes',\n key: behave[2],\n expected: behave[3],\n };\n }\n return undefined;\n}\n\nfunction normalizeCapabilityKey(key: string): string {\n return key\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_');\n}\n","import type { TableFixture } from '@fabricorg/databricks-testkit';\n\nconst INT_RE = /^-?\\d+$/;\nconst FLOAT_RE = /^-?\\d+\\.\\d+$/;\nconst TIMESTAMP_RE = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2})?/;\nconst BOOL_RE = /^(true|false)$/i;\n\n/**\n * Turn a Gherkin data table (header row + string cells) into a TableFixture,\n * inferring portable column types from the values: BIGINT, DOUBLE,\n * TIMESTAMP, BOOLEAN, else STRING. Empty cells become NULL.\n */\nexport function inferFixture(table: string, raw: readonly (readonly string[])[]): TableFixture {\n if (raw.length < 2) {\n throw new Error(`Data table for ${table} needs a header row and at least one data row`);\n }\n const [header, ...body] = raw as [readonly string[], ...(readonly string[])[]];\n const types = header.map((_, col) => inferColumnType(body.map((row) => row[col] ?? '')));\n const schema = header.map((name, i) => `\"${name}\" ${types[i]}`).join(', ');\n const rows = body.map((row) => row.map((cell, i) => coerceCell(cell, types[i] ?? 'STRING')));\n return { table, schema, rows };\n}\n\n/** Coerce data-table string cells to typed values for row matching (no header row). */\nexport function coerceMatchRows(raw: readonly (readonly string[])[]): Record<string, unknown>[] {\n if (raw.length < 2) {\n throw new Error('Match table needs a header row and at least one data row');\n }\n const [header, ...body] = raw as [readonly string[], ...(readonly string[])[]];\n return body.map((row) => {\n const out: Record<string, unknown> = {};\n header.forEach((name, i) => {\n out[name] = coerceCell(row[i] ?? '', inferColumnType([row[i] ?? '']));\n });\n return out;\n });\n}\n\nfunction inferColumnType(cells: readonly string[]): string {\n const present = cells.filter((c) => c !== '');\n if (present.length === 0) return 'STRING';\n if (present.every((c) => INT_RE.test(c))) return 'BIGINT';\n if (present.every((c) => FLOAT_RE.test(c) || INT_RE.test(c))) return 'DOUBLE';\n if (present.every((c) => TIMESTAMP_RE.test(c))) return 'TIMESTAMP';\n if (present.every((c) => BOOL_RE.test(c))) return 'BOOLEAN';\n return 'STRING';\n}\n\nfunction coerceCell(cell: string, type: string): unknown {\n if (cell === '') return null;\n if (type === 'BIGINT' || type === 'DOUBLE') return Number(cell);\n if (type === 'BOOLEAN') return /^true$/i.test(cell);\n return cell;\n}\n","import { type DatabricksRestClient, waitForDatabricksRun } from '@fabric-harness/databricks';\nimport type { WorkspaceClientLike } from '@fabricorg/databricks-testkit';\n\nexport interface RunJobOptions {\n pollIntervalMs?: number;\n timeoutMs?: number;\n}\n\nexport interface JobRunResult {\n runId: number;\n lifeCycleState: string;\n resultState: string;\n run: Record<string, unknown>;\n}\n\n/** Resolve a job by numeric id or by exact name via /api/2.1/jobs/list. */\nexport async function resolveJobId(client: WorkspaceClientLike, nameOrId: string): Promise<number> {\n if (/^\\d+$/.test(nameOrId)) return Number(nameOrId);\n const response = await client.get<{ jobs?: { job_id: number; settings?: { name?: string } }[] }>(\n '/api/2.1/jobs/list',\n { name: nameOrId },\n );\n const match = (response.jobs ?? []).find((j) => j.settings?.name === nameOrId);\n if (!match) throw new Error(`No Databricks job named '${nameOrId}' found`);\n return match.job_id;\n}\n\n/** run-now a job and poll its run to a terminal state. */\nexport async function runJobToTerminal(\n client: WorkspaceClientLike,\n nameOrId: string,\n options: RunJobOptions = {},\n): Promise<JobRunResult> {\n const jobId = await resolveJobId(client, nameOrId);\n const submitted = await client.post<{ run_id: number }>('/api/2.1/jobs/run-now', {\n job_id: jobId,\n });\n const run = await waitForDatabricksRun(client as DatabricksRestClient, submitted.run_id, {\n pollIntervalMs: options.pollIntervalMs,\n timeoutMs: options.timeoutMs,\n });\n const state = (run as { state?: { life_cycle_state?: string; result_state?: string } }).state;\n return {\n runId: submitted.run_id,\n lifeCycleState: state?.life_cycle_state ?? 'UNKNOWN',\n resultState: state?.result_state ?? 'UNKNOWN',\n run,\n };\n}\n","import {\n type PollOptions,\n type WorkspaceClientLike,\n waitForPipelineUpdate,\n} from '@fabricorg/databricks-testkit';\n\nexport type PipelineWaitOptions = PollOptions;\n\n/** Resolve a DLT/Lakeflow pipeline by id or exact name via /api/2.0/pipelines. */\nexport async function resolvePipelineId(\n client: WorkspaceClientLike,\n nameOrId: string,\n): Promise<string> {\n if (/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(nameOrId)) return nameOrId;\n const response = await client.get<{ statuses?: { pipeline_id: string; name?: string }[] }>(\n '/api/2.0/pipelines',\n { filter: `name LIKE '${nameOrId}'` },\n );\n const match = (response.statuses ?? []).find((p) => p.name === nameOrId);\n if (!match) throw new Error(`No Databricks pipeline named '${nameOrId}' found`);\n return match.pipeline_id;\n}\n\n/** Start a pipeline update; returns the update id to poll. */\nexport async function startPipelineUpdate(\n client: WorkspaceClientLike,\n pipelineId: string,\n options: { fullRefresh?: boolean } = {},\n): Promise<string> {\n const response = await client.post<{ update_id: string }>(\n `/api/2.0/pipelines/${encodeURIComponent(pipelineId)}/updates`,\n { full_refresh: options.fullRefresh ?? false },\n );\n return response.update_id;\n}\n\n/** Poll an update to a terminal state; throws on timeout or non-COMPLETED terminal states. */\nexport { waitForPipelineUpdate };\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/actions.ts
|
|
2
|
+
function defineAction(action) {
|
|
3
|
+
return action;
|
|
4
|
+
}
|
|
5
|
+
async function runAction(world, action, ...args) {
|
|
6
|
+
return await action(world, ...args);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { defineAction, runAction };
|
|
10
|
+
//# sourceMappingURL=chunk-6JC65RH2.js.map
|
|
11
|
+
//# sourceMappingURL=chunk-6JC65RH2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/actions.ts"],"names":[],"mappings":";AAeO,SAAS,aACd,MAAA,EAC8B;AAC9B,EAAA,OAAO,MAAA;AACT;AAGA,eAAsB,SAAA,CACpB,KAAA,EACA,MAAA,EAAA,GACG,IAAA,EACuB;AAC1B,EAAA,OAAQ,MAAM,MAAA,CAAO,KAAA,EAAO,GAAG,IAAI,CAAA;AACrC","file":"chunk-6JC65RH2.js","sourcesContent":["import type { DatabricksWorld } from './world.js';\n\n/**\n * A reusable, typed scenario action.\n *\n * Behave suites sometimes compose behavior with `context.execute_steps()`. In\n * TypeScript, composing ordinary functions preserves type checking, stack\n * traces, and direct unit-testability without reparsing Gherkin at runtime.\n */\nexport type ScenarioAction<Args extends readonly unknown[] = readonly unknown[], Result = void> = (\n world: DatabricksWorld,\n ...args: Args\n) => Result | Promise<Result>;\n\n/** Preserve inference while declaring a reusable scenario action. */\nexport function defineAction<Args extends readonly unknown[], Result>(\n action: ScenarioAction<Args, Result>,\n): ScenarioAction<Args, Result> {\n return action;\n}\n\n/** Execute a typed action against the current scenario World. */\nexport async function runAction<Args extends readonly unknown[], Result>(\n world: DatabricksWorld,\n action: ScenarioAction<Args, Result>,\n ...args: Args\n): Promise<Awaited<Result>> {\n return (await action(world, ...args)) as Awaited<Result>;\n}\n"]}
|