@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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type StateScope = 'run' | 'feature' | 'scenario';
|
|
2
|
+
type StateValues = Map<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Process-local state backing Behave-style run and feature context layers.
|
|
5
|
+
* Scenario values stay on the scenario World and are never shared.
|
|
6
|
+
*/
|
|
7
|
+
declare class SharedStateRegistry {
|
|
8
|
+
private readonly runValues;
|
|
9
|
+
private readonly featureValues;
|
|
10
|
+
run(): StateValues;
|
|
11
|
+
feature(featureUri: string): StateValues;
|
|
12
|
+
clear(): void;
|
|
13
|
+
}
|
|
14
|
+
/** Layered state lookup: scenario overrides feature, which overrides run. */
|
|
15
|
+
declare class ScopedState {
|
|
16
|
+
private readonly runValues;
|
|
17
|
+
private readonly featureValues;
|
|
18
|
+
private readonly scenarioValues;
|
|
19
|
+
constructor(runValues: StateValues, featureValues: StateValues);
|
|
20
|
+
set<T>(scope: StateScope, key: string, value: T): T;
|
|
21
|
+
get<T>(key: string): T | undefined;
|
|
22
|
+
require<T>(key: string): T;
|
|
23
|
+
has(key: string): boolean;
|
|
24
|
+
delete(scope: StateScope, key: string): boolean;
|
|
25
|
+
clearScenario(): void;
|
|
26
|
+
private values;
|
|
27
|
+
}
|
|
28
|
+
declare const sharedState: SharedStateRegistry;
|
|
29
|
+
|
|
30
|
+
export { ScopedState, SharedStateRegistry, type StateScope, sharedState };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type StateScope = 'run' | 'feature' | 'scenario';
|
|
2
|
+
type StateValues = Map<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Process-local state backing Behave-style run and feature context layers.
|
|
5
|
+
* Scenario values stay on the scenario World and are never shared.
|
|
6
|
+
*/
|
|
7
|
+
declare class SharedStateRegistry {
|
|
8
|
+
private readonly runValues;
|
|
9
|
+
private readonly featureValues;
|
|
10
|
+
run(): StateValues;
|
|
11
|
+
feature(featureUri: string): StateValues;
|
|
12
|
+
clear(): void;
|
|
13
|
+
}
|
|
14
|
+
/** Layered state lookup: scenario overrides feature, which overrides run. */
|
|
15
|
+
declare class ScopedState {
|
|
16
|
+
private readonly runValues;
|
|
17
|
+
private readonly featureValues;
|
|
18
|
+
private readonly scenarioValues;
|
|
19
|
+
constructor(runValues: StateValues, featureValues: StateValues);
|
|
20
|
+
set<T>(scope: StateScope, key: string, value: T): T;
|
|
21
|
+
get<T>(key: string): T | undefined;
|
|
22
|
+
require<T>(key: string): T;
|
|
23
|
+
has(key: string): boolean;
|
|
24
|
+
delete(scope: StateScope, key: string): boolean;
|
|
25
|
+
clearScenario(): void;
|
|
26
|
+
private values;
|
|
27
|
+
}
|
|
28
|
+
declare const sharedState: SharedStateRegistry;
|
|
29
|
+
|
|
30
|
+
export { ScopedState, SharedStateRegistry, type StateScope, sharedState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"scoped-state.js"}
|
package/dist/steps.cjs
CHANGED
|
@@ -323,6 +323,142 @@ function liveOnlyReason(tags, profile) {
|
|
|
323
323
|
if (blocking.length === 0) return null;
|
|
324
324
|
return `requires a live workspace (${blocking.join(", ")}); running under DBX_TEST_PROFILE=local`;
|
|
325
325
|
}
|
|
326
|
+
function capabilitiesFromEnv(profile, env = process.env) {
|
|
327
|
+
const capabilities = { profile };
|
|
328
|
+
const builtins = [
|
|
329
|
+
["cloud", env.DBX_TEST_CLOUD],
|
|
330
|
+
["compute", env.DBX_TEST_COMPUTE_MODE ?? env.DBX_TEST_COMPUTE],
|
|
331
|
+
["stage", env.DBX_TEST_STAGE]
|
|
332
|
+
];
|
|
333
|
+
for (const [key, value] of builtins) {
|
|
334
|
+
if (value !== void 0 && value !== "") capabilities[key] = value;
|
|
335
|
+
}
|
|
336
|
+
for (const [name, value] of Object.entries(env)) {
|
|
337
|
+
if (!name.startsWith("DBX_TEST_CAPABILITY_") || value === void 0) continue;
|
|
338
|
+
capabilities[normalizeCapabilityKey(name.slice("DBX_TEST_CAPABILITY_".length))] = value;
|
|
339
|
+
}
|
|
340
|
+
return capabilities;
|
|
341
|
+
}
|
|
342
|
+
function capabilityTagReason(tags, capabilities) {
|
|
343
|
+
const normalized = new Map(
|
|
344
|
+
Object.entries(capabilities).map(([key, value]) => [
|
|
345
|
+
normalizeCapabilityKey(key),
|
|
346
|
+
String(value).toLowerCase()
|
|
347
|
+
])
|
|
348
|
+
);
|
|
349
|
+
for (const tag of tags) {
|
|
350
|
+
const predicate = parseCapabilityTag(tag);
|
|
351
|
+
if (!predicate) continue;
|
|
352
|
+
const actual = normalized.get(normalizeCapabilityKey(predicate.key));
|
|
353
|
+
const matches = predicate.expected ? actual === predicate.expected.toLowerCase() : actual !== void 0 && !["", "0", "false", "no", "off"].includes(actual);
|
|
354
|
+
if (predicate.kind === "requires" && !matches) {
|
|
355
|
+
return `${tag} requires capability ${predicate.key}${predicate.expected ? `=${predicate.expected}` : ""}; actual ${actual ?? "unset"}`;
|
|
356
|
+
}
|
|
357
|
+
if (predicate.kind === "excludes" && matches) {
|
|
358
|
+
return `${tag} excludes capability ${predicate.key}${predicate.expected ? `=${predicate.expected}` : ""}`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
function scenarioSkipReason(tags, profile, env = process.env) {
|
|
364
|
+
return liveOnlyReason(tags, profile) ?? capabilityTagReason(tags, capabilitiesFromEnv(profile, env));
|
|
365
|
+
}
|
|
366
|
+
function parseCapabilityTag(tag) {
|
|
367
|
+
const direct = tag.match(/^@(requires|excludes)\.([^=]+)(?:=(.+))?$/i);
|
|
368
|
+
if (direct?.[1] && direct[2]) {
|
|
369
|
+
return {
|
|
370
|
+
kind: direct[1].toLowerCase(),
|
|
371
|
+
key: direct[2],
|
|
372
|
+
...direct[3] ? { expected: direct[3] } : {}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const behave = tag.match(/^@(use|not)\.with_([^=]+)=(.+)$/i);
|
|
376
|
+
if (behave?.[1] && behave[2] && behave[3]) {
|
|
377
|
+
return {
|
|
378
|
+
kind: behave[1].toLowerCase() === "use" ? "requires" : "excludes",
|
|
379
|
+
key: behave[2],
|
|
380
|
+
expected: behave[3]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
function normalizeCapabilityKey(key) {
|
|
386
|
+
return key.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/scoped-state.ts
|
|
390
|
+
var SharedStateRegistry = class {
|
|
391
|
+
runValues = /* @__PURE__ */ new Map();
|
|
392
|
+
featureValues = /* @__PURE__ */ new Map();
|
|
393
|
+
run() {
|
|
394
|
+
return this.runValues;
|
|
395
|
+
}
|
|
396
|
+
feature(featureUri) {
|
|
397
|
+
if (!featureUri) throw new Error("Feature state requires a feature URI");
|
|
398
|
+
let values = this.featureValues.get(featureUri);
|
|
399
|
+
if (!values) {
|
|
400
|
+
values = /* @__PURE__ */ new Map();
|
|
401
|
+
this.featureValues.set(featureUri, values);
|
|
402
|
+
}
|
|
403
|
+
return values;
|
|
404
|
+
}
|
|
405
|
+
clear() {
|
|
406
|
+
this.runValues.clear();
|
|
407
|
+
this.featureValues.clear();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var ScopedState = class {
|
|
411
|
+
constructor(runValues, featureValues) {
|
|
412
|
+
this.runValues = runValues;
|
|
413
|
+
this.featureValues = featureValues;
|
|
414
|
+
}
|
|
415
|
+
runValues;
|
|
416
|
+
featureValues;
|
|
417
|
+
scenarioValues = /* @__PURE__ */ new Map();
|
|
418
|
+
set(scope, key, value) {
|
|
419
|
+
this.values(scope).set(validateKey(key), value);
|
|
420
|
+
return value;
|
|
421
|
+
}
|
|
422
|
+
get(key) {
|
|
423
|
+
const validKey = validateKey(key);
|
|
424
|
+
if (this.scenarioValues.has(validKey)) return this.scenarioValues.get(validKey);
|
|
425
|
+
if (this.featureValues.has(validKey)) return this.featureValues.get(validKey);
|
|
426
|
+
return this.runValues.get(validKey);
|
|
427
|
+
}
|
|
428
|
+
require(key) {
|
|
429
|
+
const value = this.get(key);
|
|
430
|
+
if (value === void 0) throw new Error(`Required BDD state '${key}' is not set`);
|
|
431
|
+
return value;
|
|
432
|
+
}
|
|
433
|
+
has(key) {
|
|
434
|
+
const validKey = validateKey(key);
|
|
435
|
+
return this.scenarioValues.has(validKey) || this.featureValues.has(validKey) || this.runValues.has(validKey);
|
|
436
|
+
}
|
|
437
|
+
delete(scope, key) {
|
|
438
|
+
return this.values(scope).delete(validateKey(key));
|
|
439
|
+
}
|
|
440
|
+
clearScenario() {
|
|
441
|
+
this.scenarioValues.clear();
|
|
442
|
+
}
|
|
443
|
+
values(scope) {
|
|
444
|
+
if (scope === "run") return this.runValues;
|
|
445
|
+
if (scope === "feature") return this.featureValues;
|
|
446
|
+
return this.scenarioValues;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
function validateKey(key) {
|
|
450
|
+
const trimmed = key.trim();
|
|
451
|
+
if (!trimmed) throw new Error("BDD state key must not be empty");
|
|
452
|
+
return trimmed;
|
|
453
|
+
}
|
|
454
|
+
var sharedState = new SharedStateRegistry();
|
|
455
|
+
|
|
456
|
+
// src/actions.ts
|
|
457
|
+
async function runAction(world, action, ...args) {
|
|
458
|
+
return await action(world, ...args);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/world.ts
|
|
326
462
|
var DatabricksWorld = class extends cucumber.World {
|
|
327
463
|
profile;
|
|
328
464
|
/** Rows from the last `When I run the SQL` / `When I query table` step. */
|
|
@@ -338,10 +474,13 @@ var DatabricksWorld = class extends cucumber.World {
|
|
|
338
474
|
lastSql;
|
|
339
475
|
lastStatementId;
|
|
340
476
|
featureUri;
|
|
477
|
+
scenarioName;
|
|
478
|
+
effectiveTags = [];
|
|
341
479
|
stepOutputCapture;
|
|
342
480
|
lakebasePool;
|
|
343
481
|
ctx;
|
|
344
482
|
activeCtx;
|
|
483
|
+
scopedState;
|
|
345
484
|
cleanups = [];
|
|
346
485
|
constructor(options) {
|
|
347
486
|
super(options);
|
|
@@ -376,6 +515,29 @@ var DatabricksWorld = class extends cucumber.World {
|
|
|
376
515
|
addCleanup(cleanup) {
|
|
377
516
|
this.cleanups.push(cleanup);
|
|
378
517
|
}
|
|
518
|
+
/** Initialize Behave-style feature/scenario metadata and layered state. */
|
|
519
|
+
initializeScenario(featureUri, scenarioName, tags) {
|
|
520
|
+
this.featureUri = featureUri;
|
|
521
|
+
this.scenarioName = scenarioName;
|
|
522
|
+
this.effectiveTags = [...tags];
|
|
523
|
+
this.scopedState = new ScopedState(sharedState.run(), sharedState.feature(featureUri));
|
|
524
|
+
}
|
|
525
|
+
/** Set typed state at run, feature, or scenario scope. */
|
|
526
|
+
setState(scope, key, value) {
|
|
527
|
+
return this.state().set(scope, key, value);
|
|
528
|
+
}
|
|
529
|
+
/** Resolve state using scenario → feature → run precedence. */
|
|
530
|
+
getState(key) {
|
|
531
|
+
return this.state().get(key);
|
|
532
|
+
}
|
|
533
|
+
/** Resolve required layered state or fail with a useful message. */
|
|
534
|
+
requireState(key) {
|
|
535
|
+
return this.state().require(key);
|
|
536
|
+
}
|
|
537
|
+
/** Compose a reusable typed action without reparsing textual Gherkin. */
|
|
538
|
+
runAction(action, ...args) {
|
|
539
|
+
return runAction(this, action, ...args);
|
|
540
|
+
}
|
|
379
541
|
/** Setup a resource once for this Cucumber run and clean it up in AfterAll. */
|
|
380
542
|
runFixture(key, setup) {
|
|
381
543
|
return sharedFixtures.runFixture(key, setup);
|
|
@@ -422,8 +584,16 @@ var DatabricksWorld = class extends cucumber.World {
|
|
|
422
584
|
errors.push(error);
|
|
423
585
|
}
|
|
424
586
|
this.ctx = void 0;
|
|
587
|
+
this.scopedState?.clearScenario();
|
|
588
|
+
this.scopedState = void 0;
|
|
425
589
|
if (errors.length > 0) throw new AggregateError(errors, "Scenario cleanup failed");
|
|
426
590
|
}
|
|
591
|
+
state() {
|
|
592
|
+
if (!this.scopedState) {
|
|
593
|
+
throw new Error("BDD scoped state is only available after the scenario Before hook");
|
|
594
|
+
}
|
|
595
|
+
return this.scopedState;
|
|
596
|
+
}
|
|
427
597
|
};
|
|
428
598
|
|
|
429
599
|
// src/steps.ts
|
|
@@ -443,14 +613,18 @@ cucumber.BeforeAll(async () => {
|
|
|
443
613
|
}
|
|
444
614
|
});
|
|
445
615
|
cucumber.Before(function({ pickle }) {
|
|
446
|
-
this.featureUri = pickle.uri;
|
|
447
616
|
const tags = pickle.tags.map((t) => t.name);
|
|
448
|
-
|
|
617
|
+
this.initializeScenario(pickle.uri, pickle.name, tags);
|
|
618
|
+
const reason = scenarioSkipReason(tags, this.profile);
|
|
449
619
|
if (reason) return "skipped";
|
|
450
620
|
return void 0;
|
|
451
621
|
});
|
|
452
622
|
cucumber.AfterAll(async () => {
|
|
453
|
-
|
|
623
|
+
try {
|
|
624
|
+
await sharedFixtures.dispose();
|
|
625
|
+
} finally {
|
|
626
|
+
sharedState.clear();
|
|
627
|
+
}
|
|
454
628
|
});
|
|
455
629
|
cucumber.BeforeStep(function() {
|
|
456
630
|
const captureMode = process.env.DBX_TEST_CAPTURE_OUTPUT?.toLowerCase();
|