@scenarist/core 0.1.2 → 0.1.3
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 +179 -94
- package/dist/adapters/in-memory-registry.d.ts +2 -2
- package/dist/adapters/in-memory-sequence-tracker.d.ts +2 -2
- package/dist/adapters/in-memory-sequence-tracker.d.ts.map +1 -1
- package/dist/adapters/in-memory-sequence-tracker.js +5 -5
- package/dist/adapters/in-memory-state-manager.d.ts +1 -1
- package/dist/adapters/in-memory-state-manager.d.ts.map +1 -1
- package/dist/adapters/in-memory-state-manager.js +34 -14
- package/dist/adapters/in-memory-store.d.ts +2 -2
- package/dist/adapters/index.d.ts +4 -4
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/constants/headers.js +1 -1
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/contracts/framework-adapter.d.ts +17 -3
- package/dist/contracts/framework-adapter.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/domain/config-builder.d.ts +1 -1
- package/dist/domain/config-builder.d.ts.map +1 -1
- package/dist/domain/config-builder.js +4 -4
- package/dist/domain/index.d.ts +4 -4
- package/dist/domain/index.js +4 -4
- package/dist/domain/path-extraction.d.ts +1 -1
- package/dist/domain/path-extraction.d.ts.map +1 -1
- package/dist/domain/path-extraction.js +24 -9
- package/dist/domain/regex-matching.d.ts +1 -1
- package/dist/domain/regex-matching.js +1 -1
- package/dist/domain/response-selector.d.ts.map +1 -1
- package/dist/domain/response-selector.js +10 -5
- package/dist/domain/scenario-manager.d.ts.map +1 -1
- package/dist/domain/scenario-manager.js +3 -3
- package/dist/domain/template-replacement.d.ts.map +1 -1
- package/dist/domain/template-replacement.js +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -4
- package/dist/ports/driven/scenario-registry.d.ts +1 -1
- package/dist/ports/driven/scenario-store.d.ts +1 -1
- package/dist/ports/driven/sequence-tracker.d.ts +1 -1
- package/dist/ports/driving/scenario-manager.d.ts +1 -1
- package/dist/ports/driving/scenario-manager.d.ts.map +1 -1
- package/dist/ports/index.d.ts +7 -7
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +4 -4
- package/dist/schemas/match-criteria.d.ts +1 -1
- package/dist/schemas/match-criteria.d.ts.map +1 -1
- package/dist/schemas/match-criteria.js +5 -5
- package/dist/schemas/scenario-definition.d.ts +1 -1
- package/dist/schemas/scenario-definition.d.ts.map +1 -1
- package/dist/schemas/scenario-definition.js +27 -9
- package/dist/schemas/scenario-requests.d.ts +1 -1
- package/dist/schemas/scenario-requests.js +2 -2
- package/dist/schemas/scenarios-object.d.ts +1 -1
- package/dist/schemas/scenarios-object.js +3 -3
- package/dist/types/config.d.ts +2 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/scenario.d.ts +2 -2
- package/package.json +1 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ScenarioRegistry } from
|
|
2
|
-
import type { ScenaristScenario } from
|
|
1
|
+
import type { ScenarioRegistry } from "../ports/index.js";
|
|
2
|
+
import type { ScenaristScenario } from "../types/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* In-memory implementation of ScenarioRegistry using a Map.
|
|
5
5
|
* Suitable for single-process testing and development.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SequenceTracker, SequencePosition } from
|
|
1
|
+
import type { SequenceTracker, SequencePosition } from "../ports/driven/sequence-tracker.js";
|
|
2
2
|
/**
|
|
3
3
|
* In-memory implementation of SequenceTracker.
|
|
4
4
|
*
|
|
@@ -17,7 +17,7 @@ export declare class InMemorySequenceTracker implements SequenceTracker {
|
|
|
17
17
|
*/
|
|
18
18
|
private getKey;
|
|
19
19
|
getPosition(testId: string, scenarioId: string, mockIndex: number): SequencePosition;
|
|
20
|
-
advance(testId: string, scenarioId: string, mockIndex: number, totalResponses: number, repeatMode:
|
|
20
|
+
advance(testId: string, scenarioId: string, mockIndex: number, totalResponses: number, repeatMode: "last" | "cycle" | "none"): void;
|
|
21
21
|
reset(testId: string): void;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-memory-sequence-tracker.d.ts","sourceRoot":"","sources":["../../src/adapters/in-memory-sequence-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;GAQG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;;IAM1D;;;OAGG;IACH,OAAO,CAAC,MAAM;
|
|
1
|
+
{"version":3,"file":"in-memory-sequence-tracker.d.ts","sourceRoot":"","sources":["../../src/adapters/in-memory-sequence-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;GAQG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;;IAM1D;;;OAGG;IACH,OAAO,CAAC,MAAM;IAQd,WAAW,CACT,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,gBAAgB;IAYnB,OAAO,CACL,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GACpC,IAAI;IA0CP,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAM5B;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,QAAO,eAEhD,CAAC"}
|
|
@@ -35,21 +35,21 @@ export class InMemorySequenceTracker {
|
|
|
35
35
|
// Handle different repeat modes
|
|
36
36
|
if (nextPosition >= totalResponses) {
|
|
37
37
|
switch (repeatMode) {
|
|
38
|
-
case
|
|
38
|
+
case "last":
|
|
39
39
|
// Stay at last position (don't increment beyond last)
|
|
40
40
|
this.positions.set(key, {
|
|
41
41
|
position: totalResponses - 1,
|
|
42
42
|
exhausted: false,
|
|
43
43
|
});
|
|
44
44
|
break;
|
|
45
|
-
case
|
|
45
|
+
case "cycle":
|
|
46
46
|
// Wrap back to first position
|
|
47
47
|
this.positions.set(key, {
|
|
48
48
|
position: 0,
|
|
49
49
|
exhausted: false,
|
|
50
50
|
});
|
|
51
51
|
break;
|
|
52
|
-
case
|
|
52
|
+
case "none":
|
|
53
53
|
// Mark as exhausted
|
|
54
54
|
this.positions.set(key, {
|
|
55
55
|
position: totalResponses,
|
|
@@ -69,8 +69,8 @@ export class InMemorySequenceTracker {
|
|
|
69
69
|
reset(testId) {
|
|
70
70
|
const prefix = `${testId}:`;
|
|
71
71
|
Array.from(this.positions.keys())
|
|
72
|
-
.filter(key => key.startsWith(prefix))
|
|
73
|
-
.forEach(key => this.positions.delete(key));
|
|
72
|
+
.filter((key) => key.startsWith(prefix))
|
|
73
|
+
.forEach((key) => this.positions.delete(key));
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-memory-state-manager.d.ts","sourceRoot":"","sources":["../../src/adapters/in-memory-state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"in-memory-state-manager.d.ts","sourceRoot":"","sources":["../../src/adapters/in-memory-state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAMrE;;;;;;GAMG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IAEtE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAUzC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAyBtD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI/C,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,cAAc;IAwCtB,OAAO,CAAC,cAAc;CA4BvB;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,QAAO,YAE7C,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
2
|
+
const isDangerousKey = (key) => DANGEROUS_KEYS.has(key);
|
|
1
3
|
/**
|
|
2
4
|
* In-memory implementation of StateManager port.
|
|
3
5
|
* Fast, single-process state storage for stateful mocks.
|
|
@@ -12,20 +14,20 @@ export class InMemoryStateManager {
|
|
|
12
14
|
if (!testState) {
|
|
13
15
|
return undefined;
|
|
14
16
|
}
|
|
15
|
-
const path = key.split(
|
|
17
|
+
const path = key.split(".");
|
|
16
18
|
return this.getNestedValue(testState, path);
|
|
17
19
|
}
|
|
18
20
|
set(testId, key, value) {
|
|
19
21
|
const testState = this.getOrCreateTestState(testId);
|
|
20
22
|
// Guard: Normal set (no array syntax)
|
|
21
|
-
if (!key.endsWith(
|
|
22
|
-
const path = key.split(
|
|
23
|
+
if (!key.endsWith("[]")) {
|
|
24
|
+
const path = key.split(".");
|
|
23
25
|
this.setNestedValue(testState, path, value);
|
|
24
26
|
return;
|
|
25
27
|
}
|
|
26
28
|
// Array append syntax
|
|
27
29
|
const actualKey = key.slice(0, -2);
|
|
28
|
-
const path = actualKey.split(
|
|
30
|
+
const path = actualKey.split(".");
|
|
29
31
|
const currentValue = this.get(testId, actualKey);
|
|
30
32
|
// Guard: If current value is already an array, append immutably
|
|
31
33
|
if (Array.isArray(currentValue)) {
|
|
@@ -52,32 +54,50 @@ export class InMemoryStateManager {
|
|
|
52
54
|
setNestedValue(obj, path, value) {
|
|
53
55
|
const key = path[0];
|
|
54
56
|
// Guard: Prevent prototype pollution attacks
|
|
55
|
-
if (key
|
|
57
|
+
if (isDangerousKey(key)) {
|
|
56
58
|
return;
|
|
57
59
|
}
|
|
58
60
|
if (path.length === 1) {
|
|
59
|
-
obj
|
|
61
|
+
Object.defineProperty(obj, key, {
|
|
62
|
+
value,
|
|
63
|
+
writable: true,
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
});
|
|
60
67
|
return;
|
|
61
68
|
}
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
const existingValue = Object.hasOwn(obj, key) ? obj[key] : undefined;
|
|
70
|
+
if (typeof existingValue !== "object" ||
|
|
71
|
+
existingValue === null ||
|
|
72
|
+
Array.isArray(existingValue)) {
|
|
73
|
+
Object.defineProperty(obj, key, {
|
|
74
|
+
value: {},
|
|
75
|
+
writable: true,
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
});
|
|
64
79
|
}
|
|
65
|
-
|
|
80
|
+
const nested = obj[key];
|
|
81
|
+
this.setNestedValue(nested, path.slice(1), value);
|
|
66
82
|
}
|
|
67
83
|
getNestedValue(obj, path) {
|
|
68
84
|
const key = path[0];
|
|
69
85
|
// Guard: Prevent prototype pollution attacks
|
|
70
|
-
if (key
|
|
86
|
+
if (isDangerousKey(key)) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
// Guard: Only access own properties, not inherited ones
|
|
90
|
+
if (!Object.hasOwn(obj, key)) {
|
|
71
91
|
return undefined;
|
|
72
92
|
}
|
|
93
|
+
const value = obj[key];
|
|
73
94
|
if (path.length === 1) {
|
|
74
|
-
return
|
|
95
|
+
return value;
|
|
75
96
|
}
|
|
76
|
-
|
|
77
|
-
if (typeof nested !== 'object' || nested === null || Array.isArray(nested)) {
|
|
97
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
78
98
|
return undefined;
|
|
79
99
|
}
|
|
80
|
-
return this.getNestedValue(
|
|
100
|
+
return this.getNestedValue(value, path.slice(1));
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
103
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ScenarioStore } from
|
|
2
|
-
import type { ActiveScenario } from
|
|
1
|
+
import type { ScenarioStore } from "../ports/index.js";
|
|
2
|
+
import type { ActiveScenario } from "../types/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* In-memory implementation of ScenarioStore using a Map.
|
|
5
5
|
* Suitable for single-process testing.
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { InMemoryScenarioRegistry } from
|
|
2
|
-
export { InMemoryScenarioStore } from
|
|
3
|
-
export { InMemorySequenceTracker, createInMemorySequenceTracker } from
|
|
4
|
-
export { InMemoryStateManager, createInMemoryStateManager } from
|
|
1
|
+
export { InMemoryScenarioRegistry } from "./in-memory-registry.js";
|
|
2
|
+
export { InMemoryScenarioStore } from "./in-memory-store.js";
|
|
3
|
+
export { InMemorySequenceTracker, createInMemorySequenceTracker, } from "./in-memory-sequence-tracker.js";
|
|
4
|
+
export { InMemoryStateManager, createInMemoryStateManager, } from "./in-memory-state-manager.js";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC"}
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { InMemoryScenarioRegistry } from
|
|
2
|
-
export { InMemoryScenarioStore } from
|
|
3
|
-
export { InMemorySequenceTracker, createInMemorySequenceTracker } from
|
|
4
|
-
export { InMemoryStateManager, createInMemoryStateManager } from
|
|
1
|
+
export { InMemoryScenarioRegistry } from "./in-memory-registry.js";
|
|
2
|
+
export { InMemoryScenarioStore } from "./in-memory-store.js";
|
|
3
|
+
export { InMemorySequenceTracker, createInMemorySequenceTracker, } from "./in-memory-sequence-tracker.js";
|
|
4
|
+
export { InMemoryStateManager, createInMemoryStateManager, } from "./in-memory-state-manager.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { SCENARIST_TEST_ID_HEADER } from
|
|
1
|
+
export { SCENARIST_TEST_ID_HEADER } from "./headers.js";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/constants/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { SCENARIST_TEST_ID_HEADER } from
|
|
1
|
+
export { SCENARIST_TEST_ID_HEADER } from "./headers.js";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { ScenaristScenario, ActiveScenario, ScenaristResult, ScenaristConfigInput, ScenaristConfig, ScenaristScenarios, ScenarioIds } from
|
|
2
|
-
import type { ScenarioRegistry } from
|
|
3
|
-
import type { ScenarioStore } from
|
|
1
|
+
import type { ScenaristScenario, ActiveScenario, ScenaristResult, ScenaristConfigInput, ScenaristConfig, ScenaristScenarios, ScenarioIds } from "../types/index.js";
|
|
2
|
+
import type { ScenarioRegistry } from "../ports/driven/scenario-registry.js";
|
|
3
|
+
import type { ScenarioStore } from "../ports/driven/scenario-store.js";
|
|
4
|
+
import type { StateManager } from "../ports/driven/state-manager.js";
|
|
5
|
+
import type { SequenceTracker } from "../ports/driven/sequence-tracker.js";
|
|
4
6
|
/**
|
|
5
7
|
* Base configuration options that all framework adapters must support.
|
|
6
8
|
*
|
|
@@ -35,6 +37,18 @@ export type BaseAdapterOptions<T extends ScenaristScenarios = ScenaristScenarios
|
|
|
35
37
|
* If not provided, InMemoryScenarioStore will be used.
|
|
36
38
|
*/
|
|
37
39
|
readonly store?: ScenarioStore;
|
|
40
|
+
/**
|
|
41
|
+
* Custom state manager implementation.
|
|
42
|
+
*
|
|
43
|
+
* If not provided, an in-memory state manager will be used.
|
|
44
|
+
*/
|
|
45
|
+
readonly stateManager?: StateManager;
|
|
46
|
+
/**
|
|
47
|
+
* Custom sequence tracker implementation.
|
|
48
|
+
*
|
|
49
|
+
* If not provided, an in-memory sequence tracker will be used.
|
|
50
|
+
*/
|
|
51
|
+
readonly sequenceTracker?: SequenceTracker;
|
|
38
52
|
};
|
|
39
53
|
/**
|
|
40
54
|
* The contract that all Scenarist adapters must satisfy.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"framework-adapter.d.ts","sourceRoot":"","sources":["../../src/contracts/framework-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"framework-adapter.d.ts","sourceRoot":"","sources":["../../src/contracts/framework-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,kBAAkB,CAC5B,CAAC,SAAS,kBAAkB,GAAG,kBAAkB,IAC/C,oBAAoB,CAAC,CAAC,CAAC,GAAG;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAErC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAE/B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IAErC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;CAC5C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,CAC1B,WAAW,GAAG,OAAO,EACrB,UAAU,SAAS,kBAAkB,GAAG,kBAAkB,IACxD;IACF;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAEjC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;IAEjC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,CACvB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,WAAW,CAAC,UAAU,CAAC,KAChC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAElC;;OAEG;IACH,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;IAE3E;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,CACxB,UAAU,EAAE,WAAW,CAAC,UAAU,CAAC,KAChC,iBAAiB,GAAG,SAAS,CAAC;IAEnC;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAE/D;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjD;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAE3B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ScenaristAdapter, BaseAdapterOptions, } from
|
|
1
|
+
export type { ScenaristAdapter, BaseAdapterOptions, } from "./framework-adapter.js";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScenaristConfig, ScenaristConfigInput, ScenaristScenarios } from
|
|
1
|
+
import type { ScenaristConfig, ScenaristConfigInput, ScenaristScenarios } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Build a complete config from partial user input.
|
|
4
4
|
* Applies sensible defaults for missing values.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-builder.d.ts","sourceRoot":"","sources":["../../src/domain/config-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"config-builder.d.ts","sourceRoot":"","sources":["../../src/domain/config-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAG3B;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,kBAAkB,EACtD,OAAO,oBAAoB,CAAC,CAAC,CAAC,KAC7B,eAaF,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ScenariosObjectSchema } from
|
|
1
|
+
import { ScenariosObjectSchema } from "../schemas/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Build a complete config from partial user input.
|
|
4
4
|
* Applies sensible defaults for missing values.
|
|
@@ -12,9 +12,9 @@ export const buildConfig = (input) => {
|
|
|
12
12
|
enabled: input.enabled,
|
|
13
13
|
strictMode: input.strictMode ?? false,
|
|
14
14
|
endpoints: {
|
|
15
|
-
setScenario: input.endpoints?.setScenario ??
|
|
16
|
-
getScenario: input.endpoints?.getScenario ??
|
|
15
|
+
setScenario: input.endpoints?.setScenario ?? "/__scenario__",
|
|
16
|
+
getScenario: input.endpoints?.getScenario ?? "/__scenario__",
|
|
17
17
|
},
|
|
18
|
-
defaultTestId: input.defaultTestId ??
|
|
18
|
+
defaultTestId: input.defaultTestId ?? "default-test",
|
|
19
19
|
};
|
|
20
20
|
};
|
package/dist/domain/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { createScenarioManager } from
|
|
2
|
-
export { buildConfig } from
|
|
3
|
-
export { createResponseSelector } from
|
|
4
|
-
export { matchesRegex } from
|
|
1
|
+
export { createScenarioManager } from "./scenario-manager.js";
|
|
2
|
+
export { buildConfig } from "./config-builder.js";
|
|
3
|
+
export { createResponseSelector } from "./response-selector.js";
|
|
4
|
+
export { matchesRegex } from "./regex-matching.js";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/domain/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createScenarioManager } from
|
|
2
|
-
export { buildConfig } from
|
|
3
|
-
export { createResponseSelector } from
|
|
4
|
-
export { matchesRegex } from
|
|
1
|
+
export { createScenarioManager } from "./scenario-manager.js";
|
|
2
|
+
export { buildConfig } from "./config-builder.js";
|
|
3
|
+
export { createResponseSelector } from "./response-selector.js";
|
|
4
|
+
export { matchesRegex } from "./regex-matching.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-extraction.d.ts","sourceRoot":"","sources":["../../src/domain/path-extraction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"path-extraction.d.ts","sourceRoot":"","sources":["../../src/domain/path-extraction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAM/D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,kBAAkB,EAC3B,MAAM,MAAM,KACX,OAyBF,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
2
|
+
const isDangerousKey = (key) => DANGEROUS_KEYS.has(key);
|
|
1
3
|
/**
|
|
2
4
|
* Extracts a value from HttpRequestContext based on a path expression.
|
|
3
5
|
*
|
|
@@ -13,29 +15,34 @@
|
|
|
13
15
|
* @returns Extracted value, or undefined if path not found
|
|
14
16
|
*/
|
|
15
17
|
export const extractFromPath = (context, path) => {
|
|
16
|
-
const segments = path.split(
|
|
18
|
+
const segments = path.split(".");
|
|
17
19
|
// Guard: Need at least 2 segments (prefix.field)
|
|
18
20
|
if (segments.length < 2) {
|
|
19
21
|
return undefined;
|
|
20
22
|
}
|
|
21
23
|
const prefix = segments[0];
|
|
22
24
|
// Guard: Must be valid prefix
|
|
23
|
-
if (prefix !==
|
|
25
|
+
if (prefix !== "body" && prefix !== "headers" && prefix !== "query") {
|
|
24
26
|
return undefined;
|
|
25
27
|
}
|
|
26
28
|
const remainingPath = segments.slice(1);
|
|
27
|
-
const
|
|
28
|
-
body: context.body,
|
|
29
|
-
headers: context.headers,
|
|
30
|
-
query: context.query,
|
|
31
|
-
};
|
|
32
|
-
const source = sourceMap[prefix];
|
|
29
|
+
const source = getSourceForPrefix(context, prefix);
|
|
33
30
|
// Guard: Source must exist
|
|
34
31
|
if (source === undefined || source === null) {
|
|
35
32
|
return undefined;
|
|
36
33
|
}
|
|
37
34
|
return traversePath(source, remainingPath);
|
|
38
35
|
};
|
|
36
|
+
const getSourceForPrefix = (context, prefix) => {
|
|
37
|
+
switch (prefix) {
|
|
38
|
+
case "body":
|
|
39
|
+
return context.body;
|
|
40
|
+
case "headers":
|
|
41
|
+
return context.headers;
|
|
42
|
+
case "query":
|
|
43
|
+
return context.query;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
39
46
|
/**
|
|
40
47
|
* Traverses a nested object path.
|
|
41
48
|
*
|
|
@@ -49,10 +56,18 @@ const traversePath = (obj, path) => {
|
|
|
49
56
|
return obj;
|
|
50
57
|
}
|
|
51
58
|
// Guard: Can only traverse objects
|
|
52
|
-
if (typeof obj !==
|
|
59
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
53
60
|
return undefined;
|
|
54
61
|
}
|
|
55
62
|
const key = path[0];
|
|
63
|
+
// Guard: Prevent prototype pollution attacks
|
|
64
|
+
if (isDangerousKey(key)) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
// Guard: Only access own properties, not inherited ones
|
|
68
|
+
if (!Object.hasOwn(obj, key)) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
56
71
|
const record = obj;
|
|
57
72
|
const value = record[key];
|
|
58
73
|
// Recursively traverse remaining path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-selector.d.ts","sourceRoot":"","sources":["../../src/domain/response-selector.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"response-selector.d.ts","sourceRoot":"","sources":["../../src/domain/response-selector.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,YAAY,EACb,MAAM,mBAAmB,CAAC;AAa3B;;GAEG;AACH,KAAK,6BAA6B,GAAG;IACnC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GACjC,UAAS,6BAAkC,KAC1C,gBAiIF,CAAC"}
|
|
@@ -41,7 +41,8 @@ export const createResponseSelector = (options = {}) => {
|
|
|
41
41
|
if (matchesCriteria(context, mock.match)) {
|
|
42
42
|
// Match criteria always have higher priority than fallbacks
|
|
43
43
|
// Base specificity ensures even 1 field beats any fallback
|
|
44
|
-
const specificity = SPECIFICITY_RANGES.MATCH_CRITERIA_BASE +
|
|
44
|
+
const specificity = SPECIFICITY_RANGES.MATCH_CRITERIA_BASE +
|
|
45
|
+
calculateSpecificity(mock.match);
|
|
45
46
|
// Keep this mock if it's more specific than current best
|
|
46
47
|
// (or if no best match yet)
|
|
47
48
|
if (!bestMatch || specificity > bestMatch.specificity) {
|
|
@@ -61,7 +62,11 @@ export const createResponseSelector = (options = {}) => {
|
|
|
61
62
|
// For equal specificity fallbacks, last wins
|
|
62
63
|
// This allows active scenario mocks to override default mocks
|
|
63
64
|
// Applies to both simple fallbacks (0) and sequence fallbacks (1)
|
|
64
|
-
bestMatch = {
|
|
65
|
+
bestMatch = {
|
|
66
|
+
mockWithParams,
|
|
67
|
+
mockIndex,
|
|
68
|
+
specificity: fallbackSpecificity,
|
|
69
|
+
};
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
// Return the best matching mock
|
|
@@ -126,7 +131,7 @@ const selectResponseFromMock = (testId, scenarioId, mockIndex, mock, sequenceTra
|
|
|
126
131
|
// so position should always be valid here
|
|
127
132
|
const response = mock.sequence.responses[position];
|
|
128
133
|
// Advance position for next call
|
|
129
|
-
const repeatMode = mock.sequence.repeat ||
|
|
134
|
+
const repeatMode = mock.sequence.repeat || "last";
|
|
130
135
|
sequenceTracker.advance(testId, scenarioId, mockIndex, mock.sequence.responses.length, repeatMode);
|
|
131
136
|
return response;
|
|
132
137
|
}
|
|
@@ -216,7 +221,7 @@ const matchesBody = (requestBody, criteriaBody) => {
|
|
|
216
221
|
for (const [key, criteriaValue] of Object.entries(criteriaBody)) {
|
|
217
222
|
const requestValue = body[key];
|
|
218
223
|
// Convert to string for matching (type coercion like headers/query)
|
|
219
|
-
const stringValue = requestValue == null ?
|
|
224
|
+
const stringValue = requestValue == null ? "" : String(requestValue);
|
|
220
225
|
if (!matchesValue(stringValue, criteriaValue)) {
|
|
221
226
|
return false;
|
|
222
227
|
}
|
|
@@ -272,7 +277,7 @@ const matchesValue = (requestValue, criteriaValue) => {
|
|
|
272
277
|
return requestValue === String(criteriaValue);
|
|
273
278
|
}
|
|
274
279
|
if (criteriaValue == null) {
|
|
275
|
-
return requestValue ===
|
|
280
|
+
return requestValue === "";
|
|
276
281
|
}
|
|
277
282
|
const strategyValue = criteriaValue;
|
|
278
283
|
if (strategyValue.equals !== undefined) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario-manager.d.ts","sourceRoot":"","sources":["../../src/domain/scenario-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,YAAY,EACb,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"scenario-manager.d.ts","sourceRoot":"","sources":["../../src/domain/scenario-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,YAAY,EACb,MAAM,mBAAmB,CAAC;AAkC3B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GAAI,qDAKnC;IACD,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,KAAG,eAgFH,CAAC"}
|
|
@@ -37,9 +37,9 @@ export const createScenarioManager = ({ registry, store, stateManager, sequenceT
|
|
|
37
37
|
// Validate scenario definition at trust boundary
|
|
38
38
|
const validationResult = ScenaristScenarioSchema.safeParse(definition);
|
|
39
39
|
if (!validationResult.success) {
|
|
40
|
-
const errorMessages = validationResult.error.issues.map((err) => `${err.path.join(
|
|
41
|
-
const scenarioId = definition?.id ||
|
|
42
|
-
throw new ScenarioValidationError(`Invalid scenario definition for '${scenarioId}': ${errorMessages.join(
|
|
40
|
+
const errorMessages = validationResult.error.issues.map((err) => `${err.path.join(".")}: ${err.message}`);
|
|
41
|
+
const scenarioId = definition?.id || "<unknown>";
|
|
42
|
+
throw new ScenarioValidationError(`Invalid scenario definition for '${scenarioId}': ${errorMessages.join(", ")}`, errorMessages);
|
|
43
43
|
}
|
|
44
44
|
const existing = registry.get(definition.id);
|
|
45
45
|
// Allow re-registering the exact same scenario object (idempotent)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-replacement.d.ts","sourceRoot":"","sources":["../../src/domain/template-replacement.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"template-replacement.d.ts","sourceRoot":"","sources":["../../src/domain/template-replacement.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,OAAO,EACd,cAAc,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACpC,OA8DF,CAAC"}
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
export const applyTemplates = (value, templateData) => {
|
|
11
11
|
// Backward compatibility: If templateData doesn't have 'state' or 'params' keys,
|
|
12
12
|
// treat it as a flat state object and wrap it
|
|
13
|
-
const normalizedData =
|
|
13
|
+
const normalizedData = templateData.state !== undefined || templateData.params !== undefined
|
|
14
14
|
? templateData
|
|
15
15
|
: { state: templateData, params: {} };
|
|
16
16
|
// Guard: Handle strings (base case)
|
|
17
|
-
if (typeof value ===
|
|
17
|
+
if (typeof value === "string") {
|
|
18
18
|
// Check if entire string is a single pure template (no surrounding text)
|
|
19
19
|
// Supports both {{state.key}} and {{params.key}}
|
|
20
20
|
// Using {1,256} limit to prevent ReDoS attacks with malicious input
|
|
@@ -46,7 +46,7 @@ export const applyTemplates = (value, templateData) => {
|
|
|
46
46
|
return value.map((item) => applyTemplates(item, normalizedData));
|
|
47
47
|
}
|
|
48
48
|
// Guard: Handle objects recursively
|
|
49
|
-
if (typeof value ===
|
|
49
|
+
if (typeof value === "object" && value !== null) {
|
|
50
50
|
const result = {};
|
|
51
51
|
for (const [key, val] of Object.entries(value)) {
|
|
52
52
|
result[key] = applyTemplates(val, normalizedData);
|
|
@@ -69,19 +69,19 @@ const resolveTemplatePath = (templateData, prefix, path) => {
|
|
|
69
69
|
// Get the root object (state or params)
|
|
70
70
|
const root = templateData[prefix];
|
|
71
71
|
// Guard: Prefix doesn't exist (e.g., no params provided)
|
|
72
|
-
if (root === undefined || typeof root !==
|
|
72
|
+
if (root === undefined || typeof root !== "object" || root === null) {
|
|
73
73
|
return undefined;
|
|
74
74
|
}
|
|
75
75
|
// Resolve nested path within root object
|
|
76
|
-
const segments = path.split(
|
|
76
|
+
const segments = path.split(".");
|
|
77
77
|
let current = root;
|
|
78
78
|
for (const segment of segments) {
|
|
79
79
|
// Guard: Can't traverse non-objects
|
|
80
|
-
if (typeof current !==
|
|
80
|
+
if (typeof current !== "object" || current === null) {
|
|
81
81
|
return undefined;
|
|
82
82
|
}
|
|
83
83
|
// Handle arrays with .length property
|
|
84
|
-
if (Array.isArray(current) && segment ===
|
|
84
|
+
if (Array.isArray(current) && segment === "length") {
|
|
85
85
|
return current.length;
|
|
86
86
|
}
|
|
87
87
|
// Traverse object
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export type * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export type * from
|
|
5
|
-
export type * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
1
|
+
export type * from "./types/index.js";
|
|
2
|
+
export * from "./schemas/index.js";
|
|
3
|
+
export * from "./constants/index.js";
|
|
4
|
+
export type * from "./ports/index.js";
|
|
5
|
+
export type * from "./contracts/index.js";
|
|
6
|
+
export * from "./domain/index.js";
|
|
7
|
+
export * from "./adapters/index.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Schemas (runtime validation)
|
|
2
|
-
export * from
|
|
2
|
+
export * from "./schemas/index.js";
|
|
3
3
|
// Constants
|
|
4
|
-
export * from
|
|
4
|
+
export * from "./constants/index.js";
|
|
5
5
|
// Domain (implementations)
|
|
6
|
-
export * from
|
|
6
|
+
export * from "./domain/index.js";
|
|
7
7
|
// Adapters (default implementations)
|
|
8
|
-
export * from
|
|
8
|
+
export * from "./adapters/index.js";
|
|
@@ -36,7 +36,7 @@ export interface SequenceTracker {
|
|
|
36
36
|
* @param totalResponses - Total number of responses in the sequence
|
|
37
37
|
* @param repeatMode - Repeat behavior ('last' | 'cycle' | 'none')
|
|
38
38
|
*/
|
|
39
|
-
advance(testId: string, scenarioId: string, mockIndex: number, totalResponses: number, repeatMode:
|
|
39
|
+
advance(testId: string, scenarioId: string, mockIndex: number, totalResponses: number, repeatMode: "last" | "cycle" | "none"): void;
|
|
40
40
|
/**
|
|
41
41
|
* Reset all sequence positions for a specific test ID.
|
|
42
42
|
*
|